{"record":{"id":"0a172cb163382d5d","repo":"can1357/oh-my-pi","slug":"ssh-target-name-has-no-verified-posix-shell","errorCode":null,"errorMessage":"ssh://: ${target.name} has no verified POSIX shell for ssh:// read/write — none of sh/bash/zsh round-tripped a capability probe (use `bash` with a remote SSH command for this host)","messagePattern":"ssh://: (.+?) has no verified POSIX shell for ssh:// read/write — none of sh/bash/zsh round-tripped a capability probe \\(use `bash` with a remote SSH command for this host\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/ssh/file-transfer.ts","lineNumber":37,"sourceCode":" * wrap its snippet in `<shell> -c '…'`; OpenSSH otherwise hands the command\n * to the user's login shell, which on fish/csh/tcsh hosts can't parse our\n * `if [ … ]; then …` constructs (#3719).\n *\n * Windows hosts are refused up front — `ssh://` runs `head`/`cat`/`mv`/`test`\n * directly and cmd/powershell can't drive those. Everywhere else, we require\n * a non-empty `transferShell` (set by `probeHostInfo` after `sh -lc` /\n * `bash -lc` / `zsh -lc` round-trips a marker against the remote).\n */\nasync function ensurePosixRemote(target: SSHConnectionTarget): Promise<\"sh\" | \"bash\" | \"zsh\"> {\n\tawait ensureConnection(target);\n\tconst info = await ensureHostInfo(target);\n\tif (info.os === \"windows\") {\n\t\tthrow new Error(\n\t\t\t`ssh://: ${target.name} is a Windows host; ssh:// supports POSIX remotes only (head/cat/mv) — use \\`bash\\` with a remote SSH command for Windows hosts`,\n\t\t);\n\t}\n\tif (!info.transferShell) {\n\t\tthrow new Error(\n\t\t\t`ssh://: ${target.name} has no verified POSIX shell for ssh:// read/write — none of sh/bash/zsh round-tripped a capability probe (use \\`bash\\` with a remote SSH command for this host)`,\n\t\t);\n\t}\n\treturn info.transferShell;\n}\n\nexport interface RemoteFileReadOptions {\n\t/** Maximum bytes to materialize; the helper fetches one extra byte to detect truncation. */\n\tmaxBytes: number;\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n}\n\nexport interface RemoteFileReadResult {\n\t/** Raw file bytes, capped at `maxBytes`. */\n\tbytes: Uint8Array;\n\t/** True when the remote file was larger than `maxBytes` (`bytes` is the prefix). */\n\ttruncated: boolean;","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/ssh/file-transfer.ts#L19-L55","documentation":"Even on a non-Windows remote, ssh:// transfers require a verified POSIX shell: the library probes sh, bash, and zsh by round-tripping a marker (`shell -lc echo marker`). If none verify, this error is thrown because quoting/reliable execution for head/cat/mv cannot be guaranteed.","triggerScenarios":"Target host is POSIX-detected but its host info lacks transferShell: remotes with no standard shells in PATH for non-interactive sessions, restricted shells (rbash, nologin shell), custom minimal images (e.g. busybox with odd sh behavior or shells stripped), or a probe blocked by login banners/forced commands.","commonSituations":"Appliance/embedded devices (routers, NAS) with restricted firmware shells, accounts whose login shell is nologin/scponly, .bashrc outputting text that breaks the marker round-trip, ForceCommand in sshd_config.","solutions":["Give the account a real POSIX login shell (chsh -s /bin/bash user) on the remote","Remove anything from shell rc files that prints output on non-interactive shells (echo, motd scripts) so the marker round-trips","Check sshd_config for ForceCommand or Match restrictions blocking the probe","Fall back to the bash tool with a remote SSH command for this host","If the remote is busybox, ensure /bin/sh exists and behaves POSIXly"],"exampleFix":"# before (remote)\nuser:x:1000:1000::/home/user:/sbin/nologin\n# after (on the remote)\n$ sudo chsh -s /bin/bash user","handlingStrategy":"validation","validationCode":"import { $ } from \"bun\";\nconst MARKER = \"omp-probe-8f3a\";\nfor (const sh of [\"sh\", \"bash\", \"zsh\"]) {\n  const r = await $`ssh ${targetArg} ${sh} -lc 'echo ${MARKER}'`.quiet().nothrow();\n  if (r.exitCode === 0 && r.stdout.toString().includes(MARKER)) return; // POSIX shell verified\n}\nthrow new Error(\"no POSIX shell on remote; use bash tool with remote ssh command\");","typeGuard":null,"tryCatchPattern":"try {\n  await upload(target, path, data);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"no verified POSIX shell\")) {\n    // fall back to remote command execution for this host\n    return runRemoteCommand(target, `tee ${path} > /dev/null`);\n  }\n  throw err;\n}","preventionTips":["Ensure service accounts get /bin/bash or /bin/sh, not nologin","Keep rc files silent on non-interactive shells (guard with [[ $- == *i* ]])","Remove ForceCommand restrictions for this account if transfers are needed","Probe new hosts with `ssh host sh -lc 'echo ok'` before enabling ssh://"],"tags":["ssh","shell","posix","remote"],"backgroundTag":"ssh-no-posix-shell","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}