Skip to content

feat(session): give every tool a deadline - #104

Merged
filipeforattini merged 1 commit into
mainfrom
feat/tool-deadlines
Sep 4, 2026
Merged

feat(session): give every tool a deadline#104
filipeforattini merged 1 commit into
mainfrom
feat/tool-deadlines

Conversation

@filipeforattini

@filipeforattini filipeforattini commented Sep 4, 2026

Copy link
Copy Markdown

The gap

PR #103 ended turns that stopped producing output, but deliberately treats a tool in flight as
work — otherwise a long bash or a subagent would be killed mid-job. That leaves the opposite
hole open: a tool that never returns keeps the turn alive indefinitely, with no output and no
error. A read on a dead network mount, an LSP request to a server that stopped answering, an
MCP call to a process that went away — all of them look exactly like progress.

What this does

withOperationHooks (session/tools.ts) wraps every tool — registry, MCP, structured output —
and the call is one expression. A deadline there lands in executed.cause and flows through the
existing failure branch, so the model reads it as an ordinary output-error tool result and can
say so or try something else. Nothing crashes and the turn continues.

Two things it deliberately does not do:

  • Bound tools whose point is to take time. shell/bash carry their own deadline and let the
    model choose it, question exists to wait for a person, and task is a whole child turn with
    its own watchdog. Bounding those would cut real work.
  • Charge a tool for human deliberation. A permission dialog left open all afternoon is not a
    hung tool, so ctx.ask records how long it waited and the guard subtracts it. That is why this
    is a race with a re-checking loop rather than a plain Effect.timeoutOrElse.

Default 600000 ms — a backstop against wedging, not a performance budget. experimental.tool_timeout
overrides it; false disables.

Verification

  • New integration test in prompt.test.ts seen failing first (the turn never finished, 5 s timeout)
    and passing after: a hung read now fails as a tool error, the assistant message carries no
    abort error, and the turn completes on its own with no cancel.
  • Unit tests for the policy and the guard, including one proving a growing permission wait does not
    trip the deadline.
  • bun typecheck across all 31 packages; packages/core 1143 pass.
  • packages/redcode session/tool/effect: two failures, both reproduced on unmodified main on this
    machine and unrelated to this change — tool.write permissions (local umask 0002) and the feat(session): end a turn that has stopped producing anything #103
    watchdog test timing out under load. CI is the arbiter for the latter.

https://claude.ai/code/session_01U29Yk1UscZJ5ZVBXV1Sn8b


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

A tool that never returns held the whole turn: no output, no error, and the
turn watchdog could not intervene because a tool in flight counts as work.
Bound the one expression that wraps every tool call, so a timeout arrives at
the model as an ordinary tool failure rather than a wedge.

Exempt the tools whose whole point is to take as long as they take, and
subtract permission wait time — a dialog left open is not a hung tool.

Claude-Session: https://claude.ai/code/session_01U29Yk1UscZJ5ZVBXV1Sn8b
@filipeforattini
filipeforattini merged commit 86b2250 into main Sep 4, 2026
7 checks passed
@filipeforattini
filipeforattini deleted the feat/tool-deadlines branch September 4, 2026 04:26
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