Skip to content

Stream bash tool output incrementally instead of buffering until exit - #932

Open
erlinh wants to merge 2 commits into
mistralai:mainfrom
erlinh:stream-bash-output
Open

Stream bash tool output incrementally instead of buffering until exit#932
erlinh wants to merge 2 commits into
mistralai:mainfrom
erlinh:stream-bash-output

Conversation

@erlinh

@erlinh erlinh commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • Bash.run() used proc.communicate(), so no output was visible until
    a command finished — long-running commands (builds, tests, dev
    servers) looked stuck even though the rest of the UI stayed responsive.
  • Stdout/stderr are now drained incrementally via two reader tasks
    feeding a shared queue, emitting a ToolStreamEvent with a short,
    deduped single-line preview whenever new output appears.
  • Final result semantics (timeout behavior, max_output_bytes
    truncation, error message format, return code handling) are
    unchanged — the exact raw bytes are still buffered and decoded via
    decode_safe(...) exactly as before.

Test plan

  • Added test_streams_progress_events_for_long_running_command to
    tests/tools/test_bash.py, asserting streamed events precede the
    final BashResult and each is a single line ≤200 chars.
  • Existing tests/tools/test_bash.py, tests/acp/test_bash.py,
    tests/tools/test_ui_bash_execution.py pass (pre-existing local
    Windows-only failures around fcntl/bash-on-PATH are unrelated,
    confirmed via git stash against unmodified main).
  • Manually verified: timeout still raises the same error text,
    truncation still applies, failure message format unchanged.

erlinh added 2 commits July 22, 2026 22:58
Bash.run() previously called proc.communicate(), which blocks until
the process exits and only then yields output. Long-running commands
(builds, test suites, dev servers) therefore showed nothing in the
UI until they finished. Now stdout/stderr are drained incrementally,
emitting a live-updating ToolStreamEvent preview (deduped, single
line, <=200 chars) as new output arrives, while preserving the exact
same timeout, truncation, and error-message behavior for the final
result.
Ruff flagged run() for exceeding branch/statement/local limits, so the
preview dedup loop and reader settling move into module-level helpers.
Settling now cancels readers still blocked on a live pipe instead of
awaiting them, since an external cancellation would otherwise hang
until the process exited on its own. Also hoists the test's
function-local imports to module level and applies ruff formatting.
@erlinh
erlinh marked this pull request as ready for review July 22, 2026 20:27
@erlinh
erlinh requested a review from a team as a code owner July 22, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant