Skip to content

fix(shell): drain stdout before reading output to avoid "(no output)" on exit 0 - #35543

Closed
chethanuk wants to merge 1 commit into
anomalyco:devfrom
chethanuk:fix/issue-35511-shell-no-output-race
Closed

fix(shell): drain stdout before reading output to avoid "(no output)" on exit 0#35543
chethanuk wants to merge 1 commit into
anomalyco:devfrom
chethanuk:fix/issue-35511-shell-no-output-race

Conversation

@chethanuk

Copy link
Copy Markdown

Issue for this PR

Closes#35511 (staged on fork against dev).

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The shell tool can return (no output) with exit 0 on the first call, even when the command clearly printed to stdout.

Root cause. The merged stdout+stderr reader runs in a forkScoped fiber, but the main flow only awaits handle.exitCode. When the process exits, the enclosing Effect.scoped block closes and interrupts the reader fiber before it has drained the buffered chunks to EOF, so the assembled list is empty and the tool reports (no output) despite a clean exit.

Fix. Capture the reader fiber and, on the clean-exit branch, Fiber.join it so the merged stream drains to EOF before the scope closes. A backgrounded child can keep the stdout/stderr write end open past the parent's exit, so the join is bounded by the same abort/timeout races to avoid hanging forever. Because the process already exited cleanly on this branch, a cut-short drain is not relabelled as aborted/terminated — that would contradict the clean exit code and print a misleading "terminated after exceeding timeout" message.

Ordering that the fix guarantees:

sequenceDiagram
participant Tool as ShellTool
participant Proc as Process
participant Reader as reader fiber (forkScoped)
participant Scope as Effect.scoped
Tool->>Proc: spawn(command)
Tool->>Reader: forkScoped(Stream.runForEach handle.all)
Tool->>Tool: raceAll [exitCode, abort, timeout]
Proc-->>Tool: exitCode resolves (exit.kind = exit)
Note over Tool,Reader: Fiber.join(reader), bounded by abort/timeout
Reader-->>Tool: buffered stdout/stderr drained to EOF
Tool->>Scope: scope closes (reader already finished)
Note over Tool: output assembled from full list
Note over Tool,Scope: Before: scope closed on exit, reader interrupted mid-buffer -> "(no output)"
Loading

How did you verify your code works?

  • bun run typecheck (tsgo, packages/opencode) — passes.
  • bun test test/tool/shell.test.ts — 24 pass / 0 fail. Includes a new regression that runs echo hi 10x and asserts exit 0 with output (and metadata.output) never equal to (no output); this reliably reproduced the race before the fix.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

… on exit 0
The output reader was forked with forkScoped but only handle.exitCode was awaited, so the scope could close and interrupt the reader before buffered stdout/stderr drained to EOF, yielding "(no output)" on a clean exit 0 (anomalyco#35511).
Capture the reader fiber and, on a clean exit, join it so the merged stream drains before the scope closes. Bound the best-effort drain with the abort/timeout races so a backgrounded child holding the pipe open cannot hang the tool; a clean exit is never relabelled aborted/terminated if the drain is cut short.
@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shell tool can return (no output) with exit 0 on first call when using workdir

1 participant

@chethanuk