ssh
Execute one remote command on a discovered SSH host.
Source
- Entry:
packages/coding-agent/src/tools/ssh.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/ssh.md - Key collaborators:
packages/coding-agent/src/ssh/ssh-executor.ts— runsssh, captures outputpackages/coding-agent/src/ssh/connection-manager.ts— master-connection reuse, host probingpackages/coding-agent/src/ssh/sshfs-mount.ts— optionalsshfsmount side effectpackages/coding-agent/src/discovery/ssh.ts— discovers host configspackages/coding-agent/src/capability/ssh.ts— canonical host shapepackages/coding-agent/src/session/streaming-output.ts— tail streaming, truncation, artifactspackages/coding-agent/src/tools/tool-timeouts.ts— timeout clamp rulespackages/utils/src/dirs.ts— user/project ssh config paths
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
host |
string |
Yes | Host name key from discovered SSH config entries, not an arbitrary hostname/IP. |
command |
string |
Yes | Remote command string passed to ssh as the remote command. |
cwd |
string |
No | Remote working directory. The tool prepends a shell-specific cd/Set-Location wrapper. |
timeout |
number |
No | Timeout in seconds. Default 60; clamped to 1..3600. |
Outputs
The tool returns a standard text tool result built in packages/coding-agent/src/tools/ssh.ts:
content: one text block containing combined remote stdout+stderr, or"(no output)"when empty.details.meta.truncation: present when output exceeded the in-memory tail window; derived from the executor summary.
Streaming behavior:
- While the command runs,
onUpdatereceives tail-only text snapshots built fromTailBufferinpackages/coding-agent/src/session/streaming-output.ts. - Final output is single-shot after process exit.
Side-channel artifacts:
- When session artifact allocation is available and output exceeds the spill threshold, full output is written to a session artifact file and the returned summary carries its
artifactIdinternally. - The ssh tool itself does not print the
artifact://...URI into the result text.
Failure behavior:
- Unknown host, missing host config, timeout, cancellation, SSH startup failure, key validation failure, or non-zero remote exit all surface as thrown
ToolErrors. - Non-zero remote exit includes captured output plus
Command exited with code N.
Flow
loadSshTool()inpackages/coding-agent/src/tools/ssh.tscallsloadCapability(sshCapability.id, { cwd: session.cwd })to discover hosts.packages/coding-agent/src/discovery/ssh.tsloads host entries from, in this order: project managed ssh config, user managed ssh config,ssh.jsonin the repo root,.ssh.jsonin the repo root.getSSHConfigPath("project")andgetSSHConfigPath("user")inpackages/utils/src/dirs.tsresolve those managed files to.omp/ssh.jsonin the project and~/.omp/agent/ssh.jsonin the user config dir. This tool does not read~/.ssh/config.- Capability loading deduplicates by host name with first item winning; provider order is priority-sorted and the SSH JSON provider registers at priority
5. loadHosts()inpackages/coding-agent/src/tools/ssh.tsbuildshostsByNameand drops later duplicates again withif (!hostsByName.has(host.name)).- Tool description text is built from
packages/coding-agent/src/prompts/tools/ssh.mdplus anAvailable hosts:list. Each host entry callsgetHostInfoForHost()to show detected shell/OS when cached; otherwise it rendersdetecting.... - On execute,
SshTool.execute()rejects anyhostnot in the discovered host-name set. ensureHostInfo()inpackages/coding-agent/src/ssh/connection-manager.tsensures an SSH master connection exists, loads cached host info from disk if present, and probes remote OS/shell when cache is missing or stale.buildRemoteCommand()inpackages/coding-agent/src/tools/ssh.tsprepends a cwd change whencwdis provided:- Unix-like or Windows compat shells:
cd -- '<cwd>' && <command> - Windows PowerShell:
Set-Location -Path '<cwd>'; <command> - Windows cmd:
cd /d "<cwd>" && <command>
- Unix-like or Windows compat shells:
clampTimeout("ssh", rawTimeout)applies the1..3600second clamp frompackages/coding-agent/src/tools/tool-timeouts.ts.executeSSH()inpackages/coding-agent/src/ssh/ssh-executor.tscallsensureConnection(host)again, opportunistically mounts the remote host root withsshfsif available, optionally wraps the command inbash -corsh -cfor Windows compat mode, then spawnssshwithptree.spawn.- Output from both stdout and stderr is piped into one
OutputSink; chunks are sanitized and forwarded to streaming updates throughstreamTailUpdates(). - On normal exit, the sink returns combined output plus truncation counters. On timeout or abort,
executeSSH()returnscancelled: trueand prefixes the output with a notice line such as[SSH: ...]or[Command aborted: ...]. SshTool.execute()convertscancelled: trueintoToolError, converts non-zero exit codes intoToolError, otherwise returns the text result with truncation metadata.
Modes / Variants
- Tool unavailable:
loadSshTool()returnsnullwhen discovery finds no hosts, so the tool is not registered for that session. - Unix-like target: remote command is passed through directly, with optional
cd -- ... &&prefix. - Windows native shell: cwd wrapper uses PowerShell
Set-Locationor cmdcd /d; command otherwise runs in the remote default Windows shell. - Windows compat shell: if host probing finds
bashorshon Windows,executeSSH()wraps the remote command asbash -c '...'orsh -c '...'. Host config can force compat on/off withcompat. - Cached vs probed host info: shell/OS detection comes from in-memory cache, persisted JSON under the remote-host dir, or a fresh probe over SSH.
- Truncated vs untruncated output: small output stays in memory; large output keeps only the last 50 KiB in memory and may spill full output to an artifact file.
Side Effects
- Filesystem
- Reads managed SSH config JSON plus legacy
ssh.json/.ssh.json. - Validates private-key path existence and permissions before connecting.
- Persists probed host info as JSON under the remote-host cache dir via
persistHostInfo(). - May create the SSH control socket dir and, when
sshfsexists, remote mount dirs. - May write full command output to a session artifact file.
- Reads managed SSH config JSON plus legacy
- Network
- Opens SSH connections to the selected host.
- May issue extra probe commands to detect OS/shell and compat shells.
- Subprocesses / native bindings
- Requires
sshonPATH; spawns it for connection checks, master startup, probing, and command execution. - May call
sshfs,mountpoint,fusermount/fusermount3, orumount. - Sanitizes streamed text with
@oh-my-pi/pi-nativestext sanitization.
- Requires
- Session state (transcript, memory, jobs, checkpoints, registries)
- Uses session artifact allocation when available.
- Registers postmortem cleanup hooks for SSH master connections and sshfs mounts.
- Tool concurrency is
exclusive, so the agent scheduler should not run multiple ssh tool calls concurrently.
- Background work / cancellation
- Process spawn receives the tool
AbortSignal. - Cancellation/timeout ends the running ssh process and returns a cancelled result that the tool turns into an error.
- Process spawn receives the tool
Limits & Caps
- Timeout defaults/clamps:
default=60,min=1,max=3600inpackages/coding-agent/src/tools/tool-timeouts.ts. - Output tail window:
DEFAULT_MAX_BYTES = 50 * 1024inpackages/coding-agent/src/session/streaming-output.ts. - Output sink spill threshold defaults to the same
50 KiB; once exceeded, only the tail remains in memory. - SSH master reuse persistence:
ControlPersist=3600inpackages/coding-agent/src/ssh/connection-manager.tsandpackages/coding-agent/src/ssh/sshfs-mount.ts. - SSH host info schema version:
HOST_INFO_VERSION = 2inpackages/coding-agent/src/ssh/connection-manager.ts; stale cache entries are reprobed. - Streaming tail buffer compacts after more than
10pending chunks (MAX_PENDING) before trimming.
Errors
Unknown SSH host: ... Available hosts: ...when the model passes a host name not present in discovery.SSH host not loaded: ...if the discovered-name set andhostsByNamemap diverge.ssh binary not found on PATHwhensshis unavailable.SSH key not found: ...,SSH key is not a file: ..., orSSH key permissions must be 600 or stricter: ...from key validation.Failed to start SSH master for <target>: <stderr>when control-master startup fails.- Non-zero remote command exit becomes
ToolErrorwith captured output andCommand exited with code N. - Timeout becomes a cancelled result with output notice
[SSH: <timeout message>], thenToolError. - Abort becomes a cancelled result with output notice
[Command aborted: <message>], thenToolError. sshfsmount failures are logged and ignored inexecuteSSH(); they do not fail the tool call.- Discovery parse problems do not fail tool loading; they become capability warnings. If all sources are empty/invalid, the tool simply does not load.
Notes
- Host discovery is JSON-based only. The tool does not parse OpenSSH config files.
- Discovery expands environment variables recursively in the parsed JSON and expands
~inkey/keyPath. - Host names are capability keys; the model must pass the config key, not the raw hostname.
- Commands run without a PTY.
executeSSH()usesptree.spawn(..., { stdin: "pipe", stderr: "full" })and does not request an interactive terminal. - The tool exposes
cwdbut noenv,pty, upload, download, or explicit file-transfer fields. - Lower layers support an
artifactIdfor full output and aremotePathmount target, butSshTool.execute()does not expose those knobs. - Both stdout and stderr are merged into one output stream; ordering is whatever arrives through the two streams.
StrictHostKeyChecking=accept-newandBatchMode=yesare always set for connection checks, master startup, and command runs.- Connection reuse is keyed by discovered host name, not by raw target tuple alone.
closeAllConnections()and sshfs unmount cleanup run through postmortem hooks, not per-call teardown.