fix(agent): Stop turn timeouts looking like cancelled failures - #1331
Conversation
Timeout resumes were still feeding the model cancelled/aborted tool text, so it treated an internal execution-slice boundary as a user-visible failure. Rewrite the aborted tool payload into an active-task continuation boundary. Co-Authored-By: David Cramer <david@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Stepping back on shape: this still looks like a prompt patch on a runtime boundary, not a durable contract.
The model-facing payload after this change still contains isError: true, aborted: true, exit_code: 130, cause: "turn_deadline", and scope: "execution_slice", plus instructional prose telling the model not to report the deadline. That keeps teaching the model our internal slice mechanics. The observed failure was the model narrating runtime vocabulary; this mostly swaps "cancelled" for a longer explanation that still names the deadline.
Compare the MCP auth pause path, which parks with a short placeholder (Authorization pending.) and does not expose runtime jargon. Timeout recovery is already owned by session state (resumeReason: "timeout"), so recovery does not need the tool result to look like a failed command or to include "don't tell the user" instructions.
I'd rather replace the aborted deadline tool result with a small canonical unknown-outcome projection, for example:
- keep only what the model needs to know the prior attempt is unconfirmed (
target, maybeoutcome: "unconfirmed") - drop deadline/slice jargon from model-visible content
- avoid instructional user-facing policy text in tool payloads
- set
isErroronly if something outside model interpretation actually depends on it; if nothing does, prefer not marking a recoverable park as an error
Also, the new unit test locks the instructional string (still active, without reporting the deadline). Prefer asserting the contract: no cancelled/deadline jargon in model-visible content, and the existing component/lifecycle continuation behavior still finishes the task.
I'm fine keeping the fix local to annotateTurnDeadlineToolResult, but the projection itself should be a clean boundary object, not a rewritten cancelled failure plus coaching text.
Replace cancelled/aborted tool text and runtime deadline jargon with a small model-safe unconfirmed projection. Timeout recovery stays in session state; tests assert the projection contract and continuation behavior. Co-Authored-By: David Cramer <david@sentry.io>
Call the model-facing boundary what it is: project an unconfirmed tool result. Drop the old annotate/turn-deadline naming and simplify the afterToolCall path so timeout recovery ownership is obvious. Co-Authored-By: David Cramer <david@sentry.io>
Access aborted/target through a Record cast after the object guard so prepare/typecheck no longer fails on Property 'aborted' does not exist.
There was a problem hiding this comment.
Final pass looks good to me.
The boundary is small and honest now: aborted deadline tool results become { outcome: "unconfirmed", target? } with isError: false, recovery stays on resumeReason: "timeout", and the old annotate/turn-deadline path is fully gone.
Naming and call site read cleanly, no leftover fallbacks or coaching prose, and the unit/component split matches the contract. I'd ship it.
Co-Authored-By: David Cramer <david@sentry.io>
Record mid-slice tool aborts as a plain timed_out attempt outcome so the model knows the call did not finish, while host continuity stays on session resume and automatic continuation. Co-Authored-By: David Cramer <david@sentry.io>
Drop the invented outcome: "timed_out" dialect. Host-preempted tool attempts now project onto the same timed_out boolean bash already uses, documented on the shared tool output schema. Co-Authored-By: David Cramer <david@sentry.io>
Co-Authored-By: David Cramer <david@sentry.io>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9fb30e2. Configure here.
Keep finished sibling tool results intact after the host abort signal. Co-Authored-By: David Cramer <david@sentry.io>

When a long turn hits the serverless time limit mid-tool, Junior already parks and auto-continues on the next function. The bug was the tool result left in history: it looked cancelled (
isError, abort text), so the resumed model quit and narrated the timeout to the user.What changed
Host-preempted tool attempts now use the normal tool-result field bash already has:
{ "target": "pnpm test", "timed_out": true }timed_outis documented on the shared tool output schema. No inventedoutcomefield, no cancelled/deadline jargon,isError: false.What did not change
resumeReason: "timeout")The model sees “this attempt timed out” the same way it already understands bash command timeouts, then keeps working.