Traversio

Sharing One Connection

Run monitoring execs, an interactive shell, and SFTP on one SSH connection by giving each workflow its own channel.

The Protocol Rule

SSH does allow one authenticated connection to carry multiple channels at the same time.

The important detail is narrower than that:

  • shell, exec, and subsystem are all requests on a session channel
  • only one of those requests can succeed on a single session channel

So the right mental model is:

  • one SSHConnection
  • many child channels
  • one purpose per SSHSession

That leads to this usage model:

  • many monitoring commands in parallel should use many exec channels
  • a terminal should live on its own shell channel
  • SFTP should live on its own subsystem channel

Supported Shape

In practice:

  • many connection.execute(_:) and connection.openExec(_:) calls can overlap on one SSHConnection
  • connection.openShell(...) can stay open while other exec or SFTP work runs
  • connection.openSFTP(...) opens a separate session channel on the same SSH connection

This is the shape you want for a server-monitoring client: keep one authenticated connection alive, then open separate channels for each job type.

Limits To Remember

  • one SSHSession maps to one channel role, so shell, exec, and SFTP each use their own channel
  • when multiple tasks write to one SSHSession stdin, serialize those writes in the application layer
  • one SFTPClient can now overlap multiple request/response pairs on one subsystem, and whole-file reads or writes can keep a bounded number of SFTP requests in flight on that same channel when you opt into the concurrency knobs

Example: Run Monitoring Commands In Parallel

For one-shot monitoring commands, execute(_:) is usually the cleanest API:

import Traversio

struct ServerSnapshot: Sendable {
    let cpu: String
    let memory: String
    let disk: String
}

@available(macOS 26.0, iOS 26.0, *)
func collectSnapshot(on connection: SSHConnection) async throws -> ServerSnapshot {
    async let cpuResult = connection.execute("top -bn1 | head -n 5")
    async let memoryResult = connection.execute("free -m")
    async let diskResult = connection.execute("df -h /")

    let cpu = try await cpuResult
    let memory = try await memoryResult
    let disk = try await diskResult

    return ServerSnapshot(
        cpu: String(decoding: cpu.standardOutput, as: UTF8.self),
        memory: String(decoding: memory.standardOutput, as: UTF8.self),
        disk: String(decoding: disk.standardOutput, as: UTF8.self)
    )
}

This opens three separate exec channels on the same SSH connection and collects them independently.

Example: Keep Shell, SFTP, And Monitoring Together

If your app needs a terminal, background monitoring, and file transfer at the same time, keep one long-lived SSHConnection and open child wrappers from it:

import Traversio

@available(macOS 26.0, iOS 26.0, *)
func runWorkspace(configuration: SSHClientConfiguration) async throws {
    let connection = try await SSHClient.connect(configuration: configuration)
    let shell = try await connection.openShell()
    let sftp = try await connection.openSFTP()

    let monitorTask = Task {
        while !Task.isCancelled {
            let snapshot = try await collectSnapshot(on: connection)
            print(snapshot.cpu)
            try await Task.sleep(nanoseconds: 5_000_000_000)
        }
    }

    do {
        let version = try await sftp.currentVersionExchange()
        print("SFTP version:", version.serverVersion)

        try await shell.write("uname -a\n")
        try await shell.write("uptime\n")

        let uploadTarget = "/tmp/diagnostics.txt"
        try await sftp.writeFile(uploadTarget, data: Array("monitor ready\n".utf8))
    } catch {
        monitorTask.cancel()
        try? await shell.close()
        try? await sftp.close()
        await connection.close()
        throw error
    }

    monitorTask.cancel()
    try await shell.close()
    try await sftp.close()
    await connection.close()
}

The important part is the ownership shape:

  • connection owns the real SSH connection
  • shell owns one shell channel
  • sftp owns one subsystem channel
  • each monitoring sample opens its own exec channel

When To Open More Than One SFTP Client

A single SFTPClient can overlap independent metadata and handle requests on one subsystem.

If you later need:

  • one long upload while
  • another task lists directories or reads a file

then opening another SFTPClient from the same SSHConnection remains a good shape. Each subsystem channel gets its own flow control, and one long-running transfer can still dominate the window on a single client.

On this page