fix: skip stdin read when positional message provided#935
Conversation
`!process.stdin.isTTY` alone is too broad — it matches not just "someone piped input in" but also "the parent process inherited stdin from somewhere upstream and never closed its end." In the second case, `Bun.stdin.text()` waits forever for an EOF that never arrives. The process sits at 0% CPU with no session created, no log activity, no error — a silent hang. This surfaces in every subprocess invocation pattern that passes a positional message and inherits stdin: Claude Code's Bash tool, Python `subprocess.run(..., stdin=None)`, CI runners, plugin hosts that don't pin stdin to `/dev/null`. Downstream callers have been working around it with `stdio: "ignore"` on spawn (see e.g. altimate-opencode-plugin `plugins/altimate-code/index.ts:28`), but anything that forgets to do that still hits the wedge. Fix: only read stdin when no positional `message` was provided. Matches conventional CLI semantics (positional overrides stdin) and unblocks every subprocess caller. Pipe-only invocations without a positional arg continue to work unchanged. Closes #934
|
👋 This PR was automatically closed by our quality checks. Common reasons:
If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe run command's stdin handling is now conditional: it reads from stdin only when the process is non-TTY and no positional message was provided, preventing hangs on inherited stdin file descriptors that never close. Added comments explain the guard behavior. ChangesConditional stdin read guard
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
|
👋 This PR was automatically closed by our quality checks. Common reasons:
If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you. |
1 similar comment
|
👋 This PR was automatically closed by our quality checks. Common reasons:
If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you. |
❌ Tests — Failures DetectedTypeScript — 15 failure(s)
Next StepPlease address the failing cases above and re-run verification. cc @sahrizvi |
Closes #934
Summary
altimate-code run "<task>"hangs at 0% CPU forever when invoked as a subprocess from a context that inherits stdin without closing it (Claude Code's Bash tool, Pythonsubprocess.run, CI, plugin hosts that don't pin stdin to/dev/null). The cause is an over-broad guard atpackages/opencode/src/cli/cmd/run.ts:433:!process.stdin.isTTYmatches not just "someone piped input in" (the intended case) but also "stdin was inherited from a non-TTY parent that hasn't closed it" (the wedge case). In the second caseBun.stdin.text()waits forever for an EOF that never arrives.Fix
Only read stdin when no positional
messagewas provided:This matches conventional CLI semantics (positional argument overrides stdin —
tool "explicit arg"doesn't try to consume stdin;echo "task" | toolstill does) and unblocks every subprocess caller that passes a positional message, which is the dominant pattern in agent / skill / plugin invocations ofaltimate-code run.Downstream context
Downstream consumers have been working around the wedge by spawning altimate-code with
stdio: ["ignore", "pipe", "pipe"]— see altimate-opencode-pluginplugins/altimate-code/index.ts:28and its lineage comment at lines 177-182 explicitly referencing this bug. That workaround is fine for the plugin itself but doesn't protect any other caller (Claude Code's Bash tool, ad-hoc CI scripts, future plugins that forget). This PR is the proper fix at the source.Test plan
altimate-code-preview run "say hi" --yolo(built from a worktree with the same patch) returns in ~1.3s instead of hanging — has been running this way for ~9 days against real workloads.altimate-code run "say hi and exit" --yolofrom a Pythonsubprocess.run(..., stdin=None)parent — expect completion in seconds, not a hang.echo "hello task" | altimate-code run --yolostill reads stdin (no positional arg → guard's second conditionmessage.trim().length === 0is true → stdin is consumed).No new unit test added — there's no existing test for
cmd/run.ts, and the function is a CLI entry point that's awkward to mock at this granularity (needs spies onprocess.stdin.isTTYandBun.stdin.text()plus the surrounding setup). Happy to add one if reviewers prefer; the conditional itself is small enough that the diff is largely self-documenting.Risk
Very low. The change tightens an existing guard — any caller that should be reading stdin (no positional message) still does; the only behavior change is for callers that both pass a positional message and inherit non-TTY stdin, which is exactly the population that has been silently hanging.
Links
03-issues-and-fixes.mdIssue chore(deps): Bump @modelcontextprotocol/sdk from 1.25.2 to 1.26.0 in /packages/altimate-code #2 (in the plugin-skill-experiments deliverable)altimate-opencode-plugin/plugins/altimate-code/index.ts:28Summary by cubic
Prevented
altimate-code runfrom hanging by only reading stdin when no positional message is provided. Subprocess calls with inherited non‑TTY stdin now complete as expected while pipe-based input still works.messageis empty, updating guard inpackages/opencode/src/cli/cmd/run.ts.Bun.stdin.text()when called from tools like Pythonsubprocess.run, CI, or plugin hosts.echo "task" | altimate-code runstill reads stdin;altimate-code run "task"skips stdin.Written for commit a387257. Summary will update on new commits.
Summary by CodeRabbit