Uh oh!
There was an error while loading. Please reload this page.
fix(persona-kit): spawn opencode through a shell exec (#330) - #331
Conversation
Spawned directly by the runtime, `opencode run` fails in ~1.2s with an
opaque error and writes nothing:
Error: {"name":"UnknownError","data":{"message":"Unexpected server
error. Check server logs for details.","ref":"err_bcc367a8"}}
Its own log file (~/.local/share/opencode/log/opencode.log) is created
but stays 0 bytes. With one shell between Node and the binary — same
binary, args, cwd, env, stdin, model and credential — the identical
invocation runs normally.
Verified end to end on daily-ship: the opaque failure without the shell,
real digests posted to Slack with it (49s and 54s, first attempt).
This is empirically derived, and deliberately narrow rather than
"wrap it and hope". Ruled out by direct experiment in a live sandbox:
- PATH (the runner's already resolves opencode)
- prompt size and content (119KB synthetic and a real 72KB both fine)
- provider flakiness (6/6 consecutive clean runs)
- NODE_OPTIONS (the throttle-retry hook)
- HOME / XDG_DATA_HOME, USER / LOGNAME, cwd, FD limits (`ulimit -n 64`)
- stdin write race (stdin delayed 3s is still read correctly)
- inherited SIGPIPE disposition — re-ignoring it deliberately
(`trap "" PIPE`) still SUCCEEDED, so signal inheritance is not it
- the process group — `timeout`'s setpgid is not needed either; bash
exec with no timeout also succeeded
The shell alone is what matters. The underlying reason opencode behaves
differently under a direct execve is still unknown and worth an upstream
report; this makes the harness usable meanwhile.
`exec` replaces the shell, so the process count is unchanged, the caller
still waits on the real opencode process, and its exit code and output
are reported verbatim. Only the opencode branch is routed this way —
claude, codex and grok spawn directly and are untouched.
Refs #330
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNKZhUhEazLwGSxCG2w8KTCodex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Warning Review limit reachedNext included review available in 47 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe non-interactive opencode harness now launches through Changesopencode shell launch
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk:⚪ Minimal · up to This narrowly changes how opencode is launched while preserving argument forwarding and exit-code behavior; the other providers remain unchanged, and no actionable merge-blocking risk remains after normal checks. Sequence Diagram(s)sequenceDiagram
participant Runtime
participant Shell as /bin/sh
participant Opencode
Runtime->>Shell: spawn -c exec "$0" "$@"
Shell->>Opencode: exec opencode with run arguments
Opencode-->>Runtime: return exit status
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Linked Issues checkExplanation The changes satisfy issue ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:bef56f1462
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| bin: '/bin/sh', | ||
| args: ['-c', 'exec "$0" "$@"', interactive.bin, ...args], |
There was a problem hiding this comment.
Keep the opencode spawn portable on Windows
On win32, every non-interactive opencode invocation now asks Node to spawn the literal POSIX path /bin/sh, which is absent from a standard Windows installation, so the command fails with ENOENT before reaching opencode. This affects consumers such as the CLI persona improver, which passes spec.bin directly to spawnNonInteractiveAndCapture, and the repository otherwise contains explicit Windows execution support. Please make the wrapper platform-aware or retain direct opencode spawning on platforms where this shell is unavailable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Uh oh!
There was an error while loading. Please reload this page.
Review caught that hardcoding `/bin/sh` breaks win32: that path does not exist on a standard Windows install, so every non-interactive opencode spawn would ENOENT before reaching the binary. That reaches real consumers — the CLI persona improver passes `spec.bin` straight to spawnNonInteractiveAndCapture — and the repo supports Windows elsewhere (persona-kit/detect.ts, cli/persona-install.ts, cli/invoke/prepare-target.ts). Gate the wrapper on `process.platform !== 'win32'`, the same split the sandbox exec path already uses (runtime/src/cloud-defaults.ts:185). Windows keeps the direct spawn deliberately, not as a fallback of convenience: the failure this works around is observed in the Linux cloud sandboxes, and `cmd.exe` has no exec-replacement semantics, so wrapping there would add a process rather than replace one and break exit-code and signal passthrough. The spec test now derives the expected shape from the platform, so it asserts the wrapper on POSIX and the direct spawn on win32 instead of hardcoding one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
khaliqgant
commented
Sep 1, 2026
Good catch — this was a real break, not a theoretical one. Fixed in The CLI persona improver ( constexecThroughShell=process.platform!=='win32';return{bin: execThroughShell ? '/bin/sh' : interactive.bin,args: execThroughShell
? ['-c','exec "$0" "$@"',interactive.bin, ...args]
: args,Same platform split Windows keeps the direct spawn deliberately rather than getting a The spec test now derives the expected shape from |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#330.
The bug
Spawned directly by the runtime,
opencode runfails in ~1.2s and writes nothing:Its own log file (
~/.local/share/opencode/log/opencode.log) is created but stays 0 bytes.With one shell between Node and the binary — same binary, args, cwd, env, stdin, model and credential — the identical invocation runs normally.
The change
execreplaces the shell, so the process count is unchanged, the caller still waits on the real opencode process, and its exit code and output are reported verbatim. Only the opencode branch is routed this way — claude, codex and grok spawn directly and are untouched.Verification
End to end on
daily-ship: the opaque failure without the shell, real digests posted to Slack with it — 49s and 54s, first attempt, no retries. Reproduced across many runs in both directions.persona-kit— 43/43 passruntime(cloud-defaults,harness-process) — 10/10 passThis is empirical, and deliberately narrow
I could not determine why opencode behaves differently under a direct
execve, and the commit says so. What I can say is what it isn't — each ruled out by direct experiment in a live sandbox, not by reasoning:NODE_OPTIONS(throttle-retry hook)HOME/XDG_DATA_HOMEUSER/LOGNAME, cwd, FD limitsulimit -n 64trap "" PIPE) still succeededtimeout's setpgid)timeoutalso succeededThe last two were the strongest a-priori candidates — Node sets
SIGPIPEtoSIG_IGNand ignored dispositions surviveexecve— and both are disproven. The shell alone is what matters.Why land it anyway
Every opencode-harness persona is currently broken by this, and the failure names neither the cause nor the component. On
daily-shipit cost days: three unrelated faults (a stale opencode in the sandbox image, a credential written where opencode never reads, and a model the workspace key doesn't authorize) all surfaced as this same message.daily-shipcurrently ships its own PATH-shim workaround (AgentWorkforce/daily#37); once this lands that shim can be deleted.The upstream question — why opencode needs a shell — is worth reporting to opencode, and #330 stays open for it.
Related
foldHarnessFailureOutputfolds stderr intooutputonly on a non-zero exit, so an exit-0 failure discards the cause entirely🤖 Generated with Claude Code
https://claude.ai/code/session_01RNKZhUhEazLwGSxCG2w8KT