You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: let opencode run ignore an unused inherited stdin pipe #42064
I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
opencode run reads non-TTY stdin to EOF before starting a prompt. That is the right default for intentional input such as:
cat context.txt | opencode run "review this"
The problem is that some supervisors give every child a pipe and keep its writer open, even when the command has no stdin payload. The prompt then never starts. This minimal Python control retains the pipe deliberately; changing PIPE to DEVNULL makes the same command progress to the expected invalid-model error:
Could run expose an explicit opt-out, for example --no-stdin? It would skip only the stdin read for that invocation; the existing default and pipe + positional prompt behavior would not change. I have a small implementation and subprocess tests ready if this interface is acceptable.
The symptom also appeared in #11891, which documented DEVNULL as a caller-side workaround. Related #25508 and merged #16300 show why silently ignoring piped input is not safe. When the caller controls descriptors, DEVNULL, stdin: "ignore", or closing the writer remains the workaround.
I hit the same hang from a wrapper that leaves the child's stdin pipe open. #38723 now has a deterministic FIFO repro at run.ts:416, and the original reporter reproduced it and retracted the models.dev diagnosis.
I tested the alternative where a supplied positional message makes stdin ignored. It fixes the hang, but it also silently drops input from opencode run "prompt" < file, including the empty-positional cases. That mixed-input behavior was added explicitly, so I think --no-stdin is the safer interface.
I have subprocess tests that leave stdin open and fail by timeout on the current code, including empty positional and -- "" cases.
I hit this too: I run opencode run "<prompt>" from a nightly orchestrator script, and the child inherits a stdin pipe that the orchestrator never closes. Every run hung forever with no output; </dev/null fixed it, which is how I traced it back here.
Still reproducible on v1.19.3 and on current main. Minimal repro (isolated XDG dirs, nonexistent model, so a healthy run fails in under a second):
importos, subprocesscmd= ["opencode", "run", "--pure", "--model", "nonexistent/model", "hello"]
subprocess.run(cmd, stdin=subprocess.DEVNULL, timeout=25) # exits in ~0.7sr, w=os.pipe() # write end kept open, never writtensubprocess.run(cmd, stdin=r, timeout=25) # TimeoutExpired: hangs
Cause: stdin is read to EOF whenever it is not a TTY, even when the message is passed as an argument:
Proposed fix: I agree with @postoso that an opt-out flag is the safer interface, since ignoring stdin whenever a positional message exists would silently drop opencode run "prompt" < file. #44557 implemented --no-stdin but was closed by the stale-PR cleanup without review, and it targeted the old packages/opencode CLI, which no longer exists on main.
I have the same approach ported to the new CLI on main (3 files, +8/−1):
a --no-stdin boolean flag on run (default false, so the current behavior is unchanged);
It also adds subprocess tests against a minimal fake server: --no-stdin "hello" returns with an open, never-closing stdin; with --no-stdin, piped input is not used as the message; and piped input is still read by default. tsc -b and the existing test/run suite pass.
Happy to open the PR if a maintainer is OK with the --no-stdin interface. If it should be named or shaped differently (e.g. --stdin=false, or an env var for wrappers), I can adjust.
Confirmed unchanged on 2.1.4 (Linux x64, 2026-10-25), from a wrapper that leaves the child's stdin pipe open:
opencode run --model <model> "reply with the single word OK" with an inherited, still-open stdin pipe: no output, still running when timeout 45 killed it (rc 124).
the identical command with </dev/null: finished in 3.7 s with OK.
So the supervisor/inherited-pipe case in this report still reproduces on 2.x main. The workaround wrappers can apply today: close the child's stdin (</dev/null, stdin=subprocess.DEVNULL) and keep a timeout around the call.
Thanks for the clear write-up and the minimal reproduction.
To restate: you're asking for an explicit opt-out on opencode run, e.g. --no-stdin, that skips reading non-TTY stdin for that call. That way an inherited pipe whose writer is never closed doesn't block the prompt, and the default and pipe + positional prompt behaviour stay as they are. The current code still reads stdin to EOF whenever it isn't a TTY (packages/cli/src/run/run.ts), so this would be a new option rather than a bug fix. Maintainers will consider it.
Activity
xichen1997 commentedon Aug 13, 2026
I have draft a fix PR: #42235
postoso commentedon Aug 21, 2026
I hit the same hang from a wrapper that leaves the child's stdin pipe open. #38723 now has a deterministic FIFO repro at run.ts:416, and the original reporter reproduced it and retracted the models.dev diagnosis.
I tested the alternative where a supplied positional message makes stdin ignored. It fixes the hang, but it also silently drops input from
opencode run "prompt" < file, including the empty-positional cases. That mixed-input behavior was added explicitly, so I think--no-stdinis the safer interface.I have subprocess tests that leave stdin open and fail by timeout on the current code, including empty positional and
-- ""cases.teoxis commentedon Oct 19, 2026
I hit this too: I run
opencode run "<prompt>"from a nightly orchestrator script, and the child inherits a stdin pipe that the orchestrator never closes. Every run hung forever with no output;</dev/nullfixed it, which is how I traced it back here.Still reproducible on v1.19.3 and on current
main. Minimal repro (isolated XDG dirs, nonexistent model, so a healthy run fails in under a second):Cause: stdin is read to EOF whenever it is not a TTY, even when the message is passed as an argument:
packages/opencode/src/cli/cmd/run.ts#L417main(new CLI):packages/cli/src/run/run.ts#L76Proposed fix: I agree with @postoso that an opt-out flag is the safer interface, since ignoring stdin whenever a positional message exists would silently drop
opencode run "prompt" < file. #44557 implemented--no-stdinbut was closed by the stale-PR cleanup without review, and it targeted the oldpackages/opencodeCLI, which no longer exists onmain.I have the same approach ported to the new CLI on
main(3 files, +8/−1):--no-stdinboolean flag onrun(defaultfalse, so the current behavior is unchanged);run.ts:const piped = input.noStdin || process.stdin.isTTY ? undefined : await readStdin().It also adds subprocess tests against a minimal fake server:
--no-stdin "hello"returns with an open, never-closing stdin; with--no-stdin, piped input is not used as the message; and piped input is still read by default.tsc -band the existingtest/runsuite pass.Happy to open the PR if a maintainer is OK with the
--no-stdininterface. If it should be named or shaped differently (e.g.--stdin=false, or an env var for wrappers), I can adjust.rubenxyz commentedon Oct 25, 2026
Confirmed unchanged on 2.1.4 (Linux x64, 2026-10-25), from a wrapper that leaves the child's stdin pipe open:
opencode run --model <model> "reply with the single word OK"with an inherited, still-open stdin pipe: no output, still running whentimeout 45killed it (rc 124).</dev/null: finished in 3.7 s withOK.So the supervisor/inherited-pipe case in this report still reproduces on 2.x main. The workaround wrappers can apply today: close the child's stdin (
</dev/null,stdin=subprocess.DEVNULL) and keep atimeoutaround the call.opencodejr commentedon Oct 28, 2026
Thanks for the clear write-up and the minimal reproduction.
To restate: you're asking for an explicit opt-out on
opencode run, e.g.--no-stdin, that skips reading non-TTY stdin for that call. That way an inherited pipe whose writer is never closed doesn't block the prompt, and the default andpipe + positional promptbehaviour stay as they are. The current code still reads stdin to EOF whenever it isn't a TTY (packages/cli/src/run/run.ts), so this would be a new option rather than a bug fix. Maintainers will consider it.Related issues and PRs:
--no-stdintorun(with subprocess tests). Automated cleanup closed it without merging.stdin=DEVNULLas the caller-side workaround.Until then,
stdin=subprocess.DEVNULL,stdin: "ignore"or< /dev/nullis still the workaround when you control the child's descriptors.