{"record":{"id":"4f1663516363e816","repo":"can1357/oh-my-pi","slug":"ssh-binary-not-found-in-path","errorCode":null,"errorMessage":"ssh binary not found in PATH","messagePattern":"ssh binary not found in PATH","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/auth-broker-cli.ts","lineNumber":394,"sourceCode":"\t\tthrow new Error(\n\t\t\t`No known OAuth callback port for '${provider}'. Use device-code flow on the broker host directly.`,\n\t\t);\n\t}\n\tconst sshArgs = [\n\t\t\"-L\",\n\t\t`${port}:127.0.0.1:${port}`,\n\t\t\"-o\",\n\t\t\"ExitOnForwardFailure=yes\",\n\t\tvia,\n\t\t`${APP_NAME} auth-broker login ${provider}`,\n\t];\n\tif (dryRun) {\n\t\tprocess.stdout.write(`ssh ${sshArgs.map(a => (a.includes(\" \") ? `'${a}'` : a)).join(\" \")}\\n`);\n\t\treturn;\n\t}\n\tconst sshBin = $which(\"ssh\");\n\tif (!sshBin) {\n\t\tthrow new Error(\"ssh binary not found in PATH\");\n\t}\n\tconst proc = Bun.spawn({\n\t\tcmd: [sshBin, ...sshArgs],\n\t\tstdin: \"inherit\",\n\t\tstdout: \"inherit\",\n\t\tstderr: \"inherit\",\n\t});\n\tconst exitCode = await proc.exited;\n\tif (exitCode !== 0) {\n\t\tthrow new Error(`ssh exited with code ${exitCode}`);\n\t}\n}\n\nasync function runLogout(flags: AuthBrokerCommandArgs[\"flags\"]): Promise<void> {\n\tlet providerArg = flags.provider;\n\tconst store = await SqliteAuthCredentialStore.open(getAgentDbPath());\n\ttry {\n\t\tif (!providerArg) {","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/auth-broker-cli.ts#L376-L412","documentation":"`runRemoteLogin` spawns `ssh` to create the OAuth port-forward and first resolves the binary with `$which(\"ssh\")`. When `ssh` is not present in `PATH` the tunnel cannot be created, so it throws this pre-flight dependency error instead of a confusing spawn failure.","triggerScenarios":"Running `omp auth-broker login --via <host>` on a machine without an ssh client, with a stripped/minimal `PATH` (CI container, service manager), or where ssh lives outside `PATH`.","commonSituations":"Minimal Docker images without openssh-client; running under sudo/systemd that resets `PATH`; Windows/macOS shells where ssh is not installed or not on PATH.","solutions":["Install an SSH client (`apt install openssh-client`, `brew install openssh`, etc.).","Ensure `ssh` is on the `PATH` of the shell that runs the CLI (`which ssh`).","Run the login from an environment with a full user `PATH`, or log in directly on the broker host."],"exampleFix":"// before (PATH missing ssh)\nPATH=/usr/local/bin omp auth-broker login --via host\n// after\nexport PATH=\"$PATH:/usr/bin\" && omp auth-broker login --via host","handlingStrategy":"validation","validationCode":"import { $which } from \"@oh-my-pi/pi-utils\";\nif (!$which(\"ssh\")) {\n  console.error(\"ssh not found; install openssh-client or run login on the broker host.\");\n  process.exit(1);\n}\nawait runLogin({ provider, via: host });","typeGuard":null,"tryCatchPattern":"try {\n  await runLogin({ provider, via: host });\n} catch (err) {\n  if (err instanceof Error && err.message === \"ssh binary not found in PATH\") {\n    console.error(\"Install ssh (openssh-client) or ensure it is on PATH, then retry.\");\n  } else throw err;\n}","preventionTips":["Verify `which ssh` succeeds in the exact shell/service that runs the CLI.","Install openssh-client in containers/CI images used for remote login.","Avoid stripped `PATH`s in service definitions; set PATH explicitly."],"tags":["ssh","environment","dependency-missing"],"backgroundTag":"binary-not-found-in-path","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}