4b861eae26a339c87bc80447bca3f0cb5abddf8e
`ssh_exec` built and tore down a complete SSH connection every time it ran. Its `defer` freed the session and closed the transport before returning, so each command paid a TCP connect, a handshake, a host-key check, and a public-key or Secure Enclave signature. With a jump host the whole route was rebuilt per hop, per command: an agent turn running five commands behind one jump performed ten handshakes to do work that needs one connection and five channels. SFTP was pooled per `(runID, hostID)`, which is barely a pool — a run is short — and it had a reentrancy hole. Two tool calls that both reached the `await` on `connectSFTP` before either stored its client each built a connection, and one was never disconnected. The pool is now keyed by host alone and outlives runs and conversations, which is what makes an idle timeout meaningful: run-scoped connections are already bounded, so a sweep would rarely have fired. A follow-up message reuses the connection; three idle minutes gives it back. Exec and SFTP stay on separate sessions so a long command cannot block a directory listing — at most two connections per host, against one per command before. Two boundaries make reuse safe rather than merely cheap. A retry only happens while the command is provably not running. `channelUnavailable` is thrown for a dead session, a channel that would not open, or a `process_startup` that failed; everything after startup succeeds is rethrown unchanged. A socket that drops mid-read must not silently run half an `rm` a second time. A command that timed out or was cancelled leaves the channel mid-stream with the remote possibly still working. Closing it courteously means a blocking `libssh2_channel_close` on the queue every later command for that host has to wait behind, so the session is abandoned instead and `libssh2_session_free` reclaims the channel with it. Reuse also has to keep answering the same question. Each entry carries a route fingerprint — host, port, username, known host keys, proxy, jump chain — built only from records the store already holds. No Keychain read: this runs before every command, and a rotated credential on an unchanged identity is not a mismatch, because the live session is still the same box and the same login. Editing a host's address retires the connection bound to the old one rather than running the next command on a machine the user no longer means. No connect or disconnect tool. The model does not manage this, so putting four more definitions in every request would buy nothing. `AgentRuntime.runCleanup` goes with it: closing pooled connections was its only caller, and leaving it wired to a no-op invites someone to hang per-run teardown off a pool that is no longer per-run. The unit tests run against stubs and prove the pool's behaviour, not libssh2's. That one connection now carries five commands still has to be confirmed against a real host, in its `auth.log`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
AI-driven remote workspace for Mac, iPhone, and iPad.
https://termind.akinokaede.com
23 MiB
0 Stars
1 Watchers
0 Forks
Languages
Swift
98.9%
Python
0.8%
Ruby
0.3%