can1357/oh-my-pi · error
ssh reverse forward to ${config.sshTarget} exited with code
Error message
ssh reverse forward to ${config.sshTarget} exited with code ${early} What it means
ssh prints nothing on success, so the adapter races proc.exited against a 1.5s grace period (SSH_READY_GRACE_MS): if the ssh process is still alive after the grace window, the forward is assumed established. If ssh instead exits within that window, this error is thrown with its exit code — meaning authentication failed, the host was unreachable, the remote port was refused, or ssh options were rejected.
Source
Thrown at packages/coding-agent/src/blob-broker/exposure.ts:511
[
binary,
"-o",
"BatchMode=yes",
"-o",
"ExitOnForwardFailure=yes",
"-N",
"-R",
`${remotePort}:127.0.0.1:${port}`,
config.sshTarget,
],
{ env: process.env, stdin: "ignore", stdout: "ignore", stderr: "ignore", cwd: os.homedir() },
);
const early = await Promise.race([
proc.exited.then(code => code),
Bun.sleep(SSH_READY_GRACE_MS).then(() => null),
]);
if (early !== null) {
throw new Error(`ssh reverse forward to ${config.sshTarget} exited with code ${early}`);
}
logger.debug("blob-broker: ssh reverse forward established", {
target: config.sshTarget,
remotePort,
localPort: port,
});
return processExposure("ssh", normalizeBaseUrl(config.publicBaseUrl), proc);
}
}
}
View on GitHub (pinned to 9690622007)
Solutions
- Test `ssh -o BatchMode=yes -R 8787:localhost:0 user@host true` manually to see the real ssh error.
- Set up key-based auth and an ssh-agent so ssh never prompts.
- Check the remote sshd config allows TCP forwarding (AllowTcpForwarding yes) and the remote port is free.
- Verify sshTarget hostname/port and that outbound ssh connectivity exists.
- Use ssh options like -o StrictHostKeyChecking=accept-new / ServerAliveInterval for headless reliability.
Example fix
// before
proc spawn args without options → ssh prompts for host key and exits
// after
"exposure": { "kind": "ssh", "sshTarget": "user@host", "options": { "sshOptions": ["-o", "StrictHostKeyChecking=accept-new"] } } // plus working key auth Defensive patterns
Strategy: try-catch
Validate before calling
const probe = Bun.spawnSync(["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", sshTarget, "true"], { stderr: "pipe" });
if (probe.exitCode !== 0) throw new Error(`ssh to ${sshTarget} unavailable: ${probe.stderr.toString()}`); Try / catch
try {
const exposure = await startExposure(config);
} catch (err) {
if (err instanceof Error && err.message.startsWith("ssh reverse forward")) {
// surface ssh diagnostics: run with -v manually; check keys, agent, AllowTcpForwarding
logger.warn("ssh forward failed at startup", { target: config.sshTarget });
} else throw err;
} Prevention
- Verify BatchMode ssh works (key auth, no passphrase prompt) before enabling ssh exposure
- Ensure remote sshd allows TCP forwarding and the remote port is free
- Use StrictHostKeyChecking=accept-new and ServerAliveInterval in ssh options
- Check host reachability/DNS and that outbound port 22 is not blocked
When it happens
Trigger: startExposure kind "ssh" where the spawned `ssh -R <remotePort>:localhost:<port> <sshTarget>` process terminates within 1.5 seconds of launch.
Common situations: ssh key has a passphrase and no agent, prompting fails non-interactively; known_hosts/host-key prompt blocks and ssh exits; remote sshd has AllowTcpForwarding disabled; target hostname typo or DNS failure; remote port already bound on the server; ssh binary missing or the network blocks port 22.
Related errors
- ssh exited with code ${exitCode}
- assume-role
- sso-role
- ${argv[0]} exited with code ${exitCode} before reporting a t
- SFTP password injection is unsupported by the shared SSH tra
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0aad2509304951ec.
Report an issue: GitHub.