fix(ssh-controlmaster): prevent session_start from hanging on unreachable host

pi awaits session_start before the agent loop accepts input, so a blocking
ssh call there presents as "TUI is up but prompts are silently ignored".
The remote pwd probe and ControlMaster start had no ConnectTimeout, no
BatchMode, and no wall-clock cap, so an unreachable host (classic case:
`pi-dev --ssh <lan-host>` runs pi *inside* the devbox container, which has
no route to LAN hosts without a ProxyJump) blocked startup on the OS TCP
timeout (~75s+) while silently swallowing keystrokes.

Hardening:
- CONNECT_OPTS (ConnectTimeout=8 + ServerAlive keepalives) on every ssh
  invocation: pwd probe, master start (both key and password paths),
  sshExec, and bash exec.
- run() gains a timeoutMs option (kills child + rejects); pwd probe and both
  master-start paths bounded by STARTUP_TIMEOUT_MS (15s).
- BATCH_OPTS (BatchMode=yes) on key-auth calls so ssh never waits on a
  /dev/tty password/passphrase/host-key prompt behind the TUI; omitted under
  --ssh-ask-pass so the SSH_ASKPASS path still works. askPass flag is now
  read before the probe to pick the right opts.
- Probe failure sets an "unreachable" status + error toast and returns,
  instead of throwing an unhandled rejection from the handler.

Docs: README "How it works" + AGENTS.md technical note updated.
This commit is contained in:
2026-06-20 22:16:48 +02:00
parent 6f7dca06e8
commit aaa7d906df
3 changed files with 98 additions and 21 deletions
+2
View File
@@ -102,6 +102,8 @@ pi -e ~/src/src_local/pi-extensions/extensions/ssh-controlmaster.ts --ssh user@h
6. The system prompt is patched to tell the LLM it's operating on `<remoteCwd> (via SSH ControlMaster: <remote>)`
7. User `!` shell commands are also routed over SSH
**Hang-proofing:** every SSH invocation carries `ConnectTimeout=8` + `ServerAlive*` keepalives, and the startup probe / master start are additionally bounded by a 15 s wall-clock timeout. Key-auth calls also set `BatchMode=yes` so ssh can never wait silently on a `/dev/tty` password/passphrase or host-key prompt behind pi's TUI (`--ssh-ask-pass` omits `BatchMode` so the `SSH_ASKPASS` path still works). If the host can't be reached — e.g. a LAN target with **no route from inside a container** (`pi --ssh` needs a `ProxyJump` to reach LAN hosts from a devbox) — the probe fails fast with an `✗ unreachable` status and an error toast instead of hanging startup and silently swallowing your prompts.
The status bar shows `⚡ own master` or `⚡ system master` so you can see which path was taken.
**Status bar:** Shows `SSH ⚡ user@host:/path` when the master is ready, `⟳ connecting…` during setup, and an error state if the master fails to start.