fix(session): abort tools on deadline and keep truncated output when its file fails - #206
Merged
Merged
Conversation
…its file fails Two tool-execution robustness bugs. The tool deadline raced an `Effect.promise` around the tool call, but interrupting an `Effect.promise` does not stop the promise, and each tool runs in its own root fiber via `EffectBridge.run.promise`. On expiry the model was told the tool failed while the edit, fetch, or LSP request kept running and could land later. `ToolDeadline.guard` now hands the call a per-call `AbortSignal`, joined with `AbortSignal.any` to the turn's own signal so a stopped turn still stops the tool, and aborts it on expiry. `SessionTools` passes that signal through `ToolExecutionOptions`, so it is what tools see as `ctx.abort`; the ones that honour it now actually end. `Truncate.output` died when the managed output file could not be written (unwritable directory, full disk), turning a tool call that had already succeeded into a defect. CONTEXT.md requires an explicitly lossy bounded output without a path instead. The write failure is now caught and logged as a warning, the truncation notice says the full output could not be saved rather than pointing at a file, and `outputPath` is omitted from the result and from the tool metadata that mirrors it. `Truncate.write` keeps its die-on-failure contract for the shell tool, which manages its own file. Claude-Session: https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Tool deadline could not cancel the tool.
ToolDeadline.guardraced anEffect.promisearound the tool call. Interrupting anEffect.promisedoes not abort the underlying promise, andEffectBridge.run.promiseruns each tool in its own root fiber. On expiry the model was told the tool failed while the edit, webfetch, or LSP request kept running and could land later. Onlyshell,task,skill, andcode-modehonourctx.abort, and none of them ever saw the deadline fire.Managed output file failure killed the tool.
Truncate.outputpipedensureDir/writeFileStringthroughEffect.orDie, so an unwritable directory or a full disk turned a tool call that had already succeeded into a defect. CONTEXT.md ("Managed Tool Output File") requires an explicitly lossy bounded output without a path instead.Fix
ToolDeadline.guardnow takes the call as(abort: AbortSignal) => Effectplus an optional parentabort. It creates a per-callAbortController, joins it to the parent withAbortSignal.any(so a stopped turn still stops the tool, with no listener to clean up), and callsown.abort(reason)in the expiry path before recording the guard trip and dying.SessionToolspasses the guard's signal throughToolExecutionOptions.abortSignal, which is whatcontext()exposes asctx.abort. Tools that honour it now actually end on expiry. Unbounded tools are unchanged.Truncate.outputcatches the write failure via a typed innerretain, logs a warning with the directory and error, and returns the truncated preview with nooutputPath. The notice now says the full output could not be saved and suggests narrowing, rather than pointing at a file.Result.outputPathis optional; the five metadata sites that mirrored it (tool.ts,registry.ts, four insession/tools.ts) only emit the key when a path exists.Truncate.writekeeps its die-on-failure contract for the shell tool.Not done: wiring
ctx.abortintowebfetch. It uses Effect'sHttpClientrather thanfetch, so honouring the signal would mean racing the effect against it, which is not the trivial change the task allowed for.Test
Regression tests, both run before and after the fix:
packages/redcode/test/session/tool-deadline.test.ts: a fake tool that settles only when itsctx.abortfires; asserts the signal is aborted on expiry, the tool observes it, and the parent (turn) signal is untouched. Also: the tool sees the turn being aborted through the joined signal, and a call that finishes in time is never aborted. Before: 5 fail (the two existingguardtests adjusted to the new signature plus the three new ones). After: all pass.packages/redcode/test/tool/truncation.test.ts: replaces the truncation directory with a regular file, asserts the truncated preview is returned with nooutputPathand a notice that does not promise a file. Before: dies withAlreadyExists: FileSystem.makeDirectory. After: passes.Suites:
test/tool348 pass, 0 fail.test/session514 pass, 1 fail —prompt.test.ts"finalizes assistant when cancelled before processor creation completes" times out at its own 3000ms limit identically on unmodifiedmain, so it is pre-existing and unrelated.bun run typecheckclean. Prettier clean on changed files.https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.