Skip to content

feat(adk): let shell backends own the command timeout - #1229

Merged
hi-pender merged 8 commits into
alpha/10from
codex/fix-filesystem-bash-timeout
Aug 31, 2026
Merged

feat(adk): let shell backends own the command timeout#1229
hi-pender merged 8 commits into
alpha/10from
codex/fix-filesystem-bash-timeout

Conversation

@hi-pender

@hi-pender hi-pender commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Why

filesystem.Backend may be remote. When an agent generates a timeout for a foreground Bash command, it can only predict the command's runtime — not the network hop or the sandbox provisioning in front of it. Using that number as a caller-side wait therefore fires early on a slow link and cancels a command that had barely started.

ExecuteRequest.Timeout hands the limit to the backend, which is the only side that knows when the command actually started, and so the only side that can exclude transport and environment setup from it.

What changed

The contract (adk/filesystem/backend.go)

  • ExecuteRequest.Timeout — an optional limit on the command's own runtime. The doc now states the handover plainly: a caller may rely on this as the only bound on command execution, so a backend that ignores it will block its caller until ctx is canceled.
  • ExecuteResponse.TimedOut (new) — the backend reports that it stopped the command on that budget. Scoped deliberately: transport/RPC deadlines, ctx cancellation and environment-setup failures are errors, not timeouts. Output should still carry whatever the command produced before it was stopped.
  • Shell / StreamingShell now describe two parallel stop paths — ctx cancellation and Timeout expiry — replacing the claim that a command is stopped solely via ctx, which stopped being true once Timeout existed.

There is intentionally no framework-side backstop: a context.WithTimeout(ctx, *req.Timeout) fallback would spend the provisioning budget and fire early, reintroducing exactly the bug this fixes, and no grace period is definable without knowing the same unknowables. ctx is already the true total-wallclock bound.

Rendering

TimedOut takes precedence over the exit-code note in both the buffered and streaming renderers. A stopped command is usually also signal-killed, so it carries a non-zero exit code — the timeout is the fact the agent can act on. The streaming paths accumulate it across chunks, since the terminal chunk may carry no output.

Prompts

The timeout argument means two different things depending on the mode, so the tool description is now selected per mode, while the arg schema — derived once from struct tags — stays mode-neutral and defers to it.

Mode Description of timeout
auto-background bounds how long this call waits; on expiry the command keeps running and moves to a background task
always-foreground bounds how long the command itself may run; on expiry it is stopped and the output so far is returned

Neither variant leaks whether the backend is local or remote — that is a deployment detail the model cannot act on, and it would be wrong for a local backend sharing the same code path.

Simplification

The three-value run-mode enum collapsed into a single backendOwnsCommandTimeout bool. The explicit-background member was redundant: RunInBackground short-circuits before any foreground wait, so its timeout is ignored either way.

Docs

  • LocalBackgroundConfig.AlwaysForeground now says that setting it disables the Manager's foreground timer, leaving ctx as the only bound, and that ShouldAutoBackground is therefore never consulted.
  • The non-positive ForegroundTimeoutMs sentinel is documented on backgroundlocal.Config / Input, where it is actually read, and the ForegroundTimeoutMsShouldAutoBackground coupling is noted on the managed-tool config.

Timeout behavior

Mode run_in_background Bash timeout
AlwaysForeground=false false ForegroundTimeoutMs — the wait before stop or auto-background handoff
AlwaysForeground=true false ExecuteRequest.Timeout; foreground timer disabled
Any true Ignored; the command starts in the background immediately

Compatibility

  • ExecuteRequest.Timeout, ExecuteResponse.TimedOut and LocalBackgroundConfig.AlwaysForeground are all optional; the zero values preserve current behavior.
  • Buffered and streaming backends use the same mapping.
  • A backend that does not set TimedOut renders exactly as before.
  • AlwaysForeground is not yet plumbed through adk/prebuilt/deep's LocalShellConfig, so deep callers cannot reach the new mode.

Tests

New coverage: TimedOut rendering (unit, buffered end-to-end, and streaming where the timeout arrives on an output-less terminal chunk), execTerminalNote precedence, the backendOwnsCommandTimeout truth table, the explicit-background branch of managedRunInput, and per-mode description selection including the custom-description override.

  • go test -race ./adk/... -count=1
  • go vet ./adk/...
  • gofmt -s -l ./adk/
  • golangci-lint run ./adk/... — 0 issues

Comment thread adk/middlewares/filesystem/bash_run.go Outdated
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.92308% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (alpha/10@37cadf6). Learn more about missing BASE report.

Files with missing lines Patch % Lines
adk/middlewares/filesystem/filesystem.go 92.59% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             alpha/10    #1229   +/-   ##
===========================================
  Coverage            ?   81.90%           
===========================================
  Files               ?      219           
  Lines               ?    37230           
  Branches            ?        0           
===========================================
  Hits                ?    30493           
  Misses              ?     4538           
  Partials            ?     2199           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hi-pender hi-pender changed the title fix(adk): pass bash timeout to shell backends feat(adk): add optional shell execution timeout Aug 29, 2026
@hi-pender hi-pender changed the title feat(adk): add optional shell execution timeout feat(adk): support shell command timeout modes Aug 29, 2026
hi-pender and others added 5 commits August 29, 2026 20:06
filesystem.Backend may be remote, so an agent-generated bash timeout can only
predict the command's own runtime, not the network hop or sandbox provisioning
in front of it. ExecuteRequest.Timeout hands that limit to the backend, which is
the only side that knows when the command actually started.

Complete the handover in both directions:

- Add ExecuteResponse.TimedOut so a backend can report that it stopped the
  command on that budget. The note takes precedence over the exit-code note in
  both the buffered and streaming renderers: a stopped command is usually also
  signal-killed, and the timeout is the fact the agent can act on.
- Document the contract instead of re-taking it. Timeout is the only bound on
  command execution, and Shell/StreamingShell now describe two parallel stop
  paths (ctx cancellation, Timeout expiry) rather than the stale ctx-only claim.
  No framework-side deadline: a ctx timeout equal to Timeout would spend the
  provisioning budget and fire early, reintroducing the bug this fixes.
- Split the tool description per mode. The timeout argument bounds the caller's
  wait in auto-background mode and the command's own runtime in always-foreground
  mode; the description is fixed at construction, while the arg schema is derived
  once from struct tags, so the schema stays mode-neutral and defers to it.
- Collapse the three-value run-mode enum into a single bool. The explicit
  background member was redundant: RunInBackground short-circuits before any
  foreground wait, so its timeout is ignored either way.

Also document the non-positive ForegroundTimeoutMs sentinel where it is read,
and that it makes ShouldAutoBackground unreachable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hi-pender hi-pender changed the title feat(adk): support shell command timeout modes feat(adk): let shell backends own the command timeout Aug 31, 2026
@hi-pender
hi-pender merged commit a4bcb2d into alpha/10 Aug 31, 2026
16 checks passed
@hi-pender
hi-pender deleted the codex/fix-filesystem-bash-timeout branch August 31, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants