Skip to content

feat(shell): stream shell_execute output to fix hard 90s timeout#1360

Merged
Aaronontheweb merged 6 commits into
netclaw-dev:devfrom
Aaronontheweb:claude-wt-tool-timeout-budget
Jun 8, 2026
Merged

feat(shell): stream shell_execute output to fix hard 90s timeout#1360
Aaronontheweb merged 6 commits into
netclaw-dev:devfrom
Aaronontheweb:claude-wt-tool-timeout-budget

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Summary

Design

  • ExecuteStreamAsync → thin channel-relay iterator (works around C# yield-in-catch restriction)
  • ExecuteStreamCoreAsync → non-iterator core: validates, starts process, drains pipes via two background tasks, relays ToolActivityUpdate items, assembles final ToolCompletedUpdate
  • DrainPipeToChannelAsync → reads pipe with CancellationToken.None (pipe-deadlock safety), feeds chunks to accumulator AND coalesces into activity items at 500ms intervals
  • Existing ExecuteAsync is unchanged — still used by tests and the base-class streaming adapter

What changed

File Change
ShellTool.cs Added ExecuteStreamAsync, ExecuteStreamCoreAsync, DrainPipeToChannelAsync, KillAndDrainAsync
BoundedOutputReader.cs Extracted BoundedOutputAccumulator; refactored DrainToWindowAsync to use it
ShellToolStreamingTests.cs 8 new tests: activity emission, stderr phase, chatty command, cancellation, output clamping, validation errors, format parity
BoundedOutputReaderTests.cs 4 new accumulator tests

Test plan

  • All 2,223 existing tests pass (0 failures, 0 skipped)
  • 8 new streaming tests cover: echo with activity+completion, stderr phase, chatty multi-activity, cancellation/timeout, output clamping, empty command error, hard deny error, streaming-matches-non-streaming format
  • 4 new accumulator tests: short verbatim, long truncation, ring wrap, parity with DrainToWindowAsync
  • dotnet slopwatch analyze — 0 new violations
  • ./scripts/Add-FileHeaders.ps1 -Verify — all headers present
  • Manual: run a long build command (>90s with steady output) through a session — verify it completes without timeout
  • Manual: run a short command (echo hello) — verify activity items + completion

Closes #1356
Relates to #1046

netclaw-dev#1356, netclaw-dev#1046)

ShellTool was non-streaming — the StreamingToolWatchdog received no
activity items, so its inactivity budget acted as a hard wall-clock cap
(90s default) rather than a silence detector.

Override ExecuteStreamAsync to emit stdout/stderr chunks as
ToolActivityUpdate items via a channel-based pattern. Each chunk resets
the watchdog timer, so the 90s budget now measures silence between
output, not total execution time. Chatty commands run indefinitely;
silent commands use the existing _timeout_seconds mechanism (up to 600s).

Extract BoundedOutputAccumulator from BoundedOutputReader so the
streaming path can feed pipe chunks to both the activity channel and the
head+tail window simultaneously without duplicating ring buffer logic.
…eout message

- Guard WaitForExitAsync with process.HasExited so a token that fires
  between pipe-close and exit-status assembles valid output instead of
  discarding it as a spurious timeout.
- Add outer catch-all in ExecuteStreamCoreAsync so unexpected exceptions
  surface as a tool-result error instead of silently faulting the
  fire-and-forget task.
- Compute effectiveTimeoutSeconds in the streaming path and include the
  duration in the timeout message, matching the non-streaming path.
Slopwatch SW003 flagged the empty catch (InvalidOperationException) block
in KillAndDrainAsync. Add a Debug.WriteLine matching the non-streaming
path's pattern for the already-exited-before-kill case.
ShellTool uses cmd.exe on Windows, not bash. Replace bash-only syntax
(for/do/done, >&2, sleep, seq) with platform-aware commands so tests
pass on both the Linux and Windows CI runners.
effectiveTimeoutSeconds was computed but only used in the error message.
Add a CancellationTokenSource.CancelAfter linked with the caller's ct
so _timeout_seconds acts as an absolute wall-clock cap while the
watchdog's inactivity budget handles silence detection independently.
@Aaronontheweb Aaronontheweb merged commit 608767f into netclaw-dev:dev Jun 8, 2026
15 checks passed
@Aaronontheweb Aaronontheweb deleted the claude-wt-tool-timeout-budget branch June 8, 2026 17:18
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.

Tool execution framework hard 90s timeout ignores _timeout_seconds parameter

1 participant