Skip to content

Foreground bash gives zero output until exit, so long commands look like the harness has frozen #472

Description

@justrach

What happens

A foreground bash tool call returns nothing at all until the process exits. For a command that takes minutes — a test suite, a build, a deploy — the session goes completely silent, with no output, no elapsed time, and no indication anything is alive. From the outside it is indistinguishable from a hang, and it blocks the whole turn: nothing else can happen while it runs.

Reported by a user mid-session as "why does codegraff always hang here tho" and "it js stops everything".

Evidence from one real session

Harness v0.0.243-4-g18e3f31-dirty, measured from one session's local event trace:

  • 9 bash calls over 60s
  • 4302s (72 min) of wall time inside those 9 calls
  • 5120s (85 min) total tool wall time across 198 calls

So ~84% of all tool time was spent in blocking calls that displayed nothing while running. Individual offenders:

ms what it was
2977129 (50 min) a vendor CLI's log-streaming subcommand — streams, never exits, had to be cancelled by the user
355904 (5.9 min) vitest run against a remote database
260087 same suite
259741 same suite
163511 vitest run single file

Why it is worse than "just slow"

  1. No partial output. The command is producing output the whole time; none of it is shown. The buffer is only flushed on exit.
  2. Streaming commands never exit at all. A log-streaming CLI subcommand, tail -f, a dev server — these have no exit. In the foreground they consume the turn until a human notices and cancels. The 50-minute call above is exactly this.
  3. The documented workaround is invisible at the call site. run_in_background + bash_output exists and works, but nothing about a foreground call signals "this one is going to be long" until it is already too late. An agent only learns after burning the time.
  4. Piping defeats the workaround too. Backgrounding a job but piping through tail -40 reproduces the silence, because tail buffers to EOF — so bash_output returns "(no new output)" for minutes on a job that is working fine. Easy trap to fall into.

Suggested fixes, roughly in order of value

  • Stream partial stdout/stderr for foreground bash as it arrives, even truncated to the last N lines, instead of buffering to exit.
  • Auto-promote to background after a threshold (say 30-60s): return a job id and the output so far, with a note that it kept running, rather than holding the turn open indefinitely.
  • Emit a heartbeat — elapsed seconds and bytes-so-far — so a silent-but-working command is distinguishable from a wedged one.
  • Warn on obviously-streaming commands (logs, tail -f, dev, watch) by suggesting run_in_background at the call site.
  • Document the | tail buffering trap in the bash tool description, since it silently defeats bash_output.

Impact

On any task that runs a real project's own test/build/deploy commands — which the system prompt explicitly asks for — this is the dominant source of dead time and the main thing that makes the harness feel broken to a watching user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions