dssh reaches host-LAN peers from inside the devbox container because it runs
`ssh -F ~/.ssh-local/config` (which Includes the host-owned, bind-mounted
ssh-lan.conf carrying `ProxyJump host` entries). pi --ssh shelled out to plain
`ssh`/`ssh -G` against the default ~/.ssh/config, which has no jump, so it
could not reach peers the host can.
Thread `-F <config>` through every ssh call (ssh -G, pwd probe, master start
for both key and password paths, sshExec, bash exec, ssh -O exit), resolved
once at load by resolveSshConfigOpts():
PI_SSH_CONFIG (leading ~ expanded, honored even if missing)
else ~/.ssh-local/config if present
else [] (no -F)
No hostnames are baked into the image — the LAN list lives only in the
host-owned, read-only-mounted ~/.config/devbox-shell/ssh-lan.conf. On the host
(native pi) ~/.ssh-local/config doesn't exist, so -F is omitted and behavior is
unchanged. Command-line -o options still win over -F, so own-master /tmp socket
and ControlMaster decisions are unaffected. Status/notify shows [config: <path>]
when a non-default config is used.
Verified from the container: the patched probe reaches an enrolled peer (pve ->
/root) where plain ssh times out. Reaching a new peer (e.g. alpserv-2) is now a
one-line host-side edit to ssh-lan.conf.
Docs: README + AGENTS.md updated.
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.
The devbox bind-mounts ~/.ssh read-only. A user ~/.ssh/config with a per-host
ControlPath under it (the CGNAT idiom `ControlPath ~/.ssh/cm/%r@%h:%p`) is
unwritable there, so a plain `ssh <host> pwd` exits 255 trying to bind the
master socket — blocking `pi --ssh <host>` with "Could not resolve remote pwd".
A system default cannot override a user's per-host value (SSH first-value-wins),
so this must be handled in the extension.
- controlPathWritable(): expands ~, tests whether the socket's parent dir is
writable (or missing but creatable via nearest existing ancestor). Pure fs
check — OS-agnostic, no host-OS detection.
- negotiateMaster / negotiateMasterWithPassword: reuse the system master only
when its ControlPath is writable; otherwise start our own /tmp master whose
command-line `-o ControlPath` overrides the user's unwritable path.
- Remote pwd probe: `-o ControlPath=none -o ControlMaster=no` so a read-only
system ControlPath cannot make the initial probe fail.
No behaviour change for configs without ControlMaster. Updates README.md +
AGENTS.md to match.
Pi moved to its new home at earendil-works on 2026-05-07
(https://pi.dev/news/2026/5/7/pi-has-a-new-home). Affected packages:
@mariozechner/pi-coding-agent -> @earendil-works/pi-coding-agent
@mariozechner/pi-tui -> @earendil-works/pi-tui
@mariozechner/pi-ai -> @earendil-works/pi-ai
@mariozechner/pi-agent-core -> @earendil-works/pi-agent-core
The old @mariozechner/* packages are deprecated on npm with the
explicit message 'please use @earendil-works/pi-coding-agent instead
going forward', and the version stream has moved on (old top-out
0.73.1; new currently 0.74.0). Anyone npm-installing the old names
gets a deprecation warning + a stale binary.
Sweep:
- All 7 extension TypeScript files: import statements updated.
- README, AGENTS, install.sh: textual references and the github.com/
mariozechner/pi-coding-agent URL pointed at github.com/earendil-works/
pi (the new monorepo root; coding-agent now lives at
packages/coding-agent inside it).
- Bun build of mcp-loader, ext-toggle, ssh-controlmaster verified clean.
Brew install references (`brew install pi-coding-agent`) left as-is:
the homebrew formula still works at 0.73.1 and a tap update is
tracked upstream at earendil-works/pi#2755. Historical CHANGELOG
entries are untouched.