fix(tools): thrown tool errors match original wire+display format#705
Merged
Conversation
A Read of a directory printed the raw wire markup in the transcript:
Error: <tool_use_error>path is not a file: /ws/obama-blog/src</tool_use_error>
The original never shows those bytes, on two independent grounds this
port had missed:
1. Wire parity — thrown call() errors are sent RAW. TS formatError
(utils/toolErrors.ts:5) returns the message (+ stderr/stdout parts,
40KB middle truncation) and is_error:true carries the error-ness
(toolExecution.ts:1633+1674). <tool_use_error> tags are reserved for
pre-execution failures (no-such-tool, InputValidationError,
validateInput), which keep them — identical to TS.
2. Display parity — the transcript renderer strips error markup anyway.
Port of FallbackToolUseErrorMessage.tsx (extractTag tool_use_error →
drop sandbox_violations → strip <error> tags → InputValidationError
collapses to "Invalid tool parameters") plus each tool's
renderToolUseErrorMessage collapse (File not found / Error reading
file / File must be read first / …) with extractTag ported verbatim
from utils/messages.ts:635.
Supporting changes:
- OpenAI-wire converters (chat-completions, Responses, Gemini) now
prefix "Error: " on is_error tool results, mirroring TS
convertToolResultContent (openaiShim.ts:309) — role=tool has no
is_error field, so with the tags gone the prefix is the only failure
signal those models get.
- Read on a directory raises the exact TS message (readFileInRange.ts:89):
EISDIR: illegal operation on a directory, read '<path>' — models know
this string from original transcripts and recover by listing. Other
non-files (FIFOs/sockets) keep the generic message: TS streams those,
we refuse (pre-existing deliberate divergence).
- Overflow hint pluralizes ("+1 line") like the original.
Suites: pytest 8529 passed / 0 failed (15 new); vitest +8 passed,
pre-existing failures byte-identical to main; tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
A
Readof a directory printed raw wire markup in the transcript:The original Claude Code shows
Error: EISDIR: illegal operation on a directory, read '/…/src'— no tags. This port had missed that on two independent layers, plus two supporting gaps:call()errors are sent RAW in the original:content = formatError(error)withis_error: true(toolExecution.ts:1633+1674).<tool_use_error>tags are reserved for pre-execution failures (no-such-tool, InputValidationError, validateInput) — those keep their tags, identical to TS._format_erroris now a faithfulformatErrorport (stderr/stdout parts,Command failed with no outputfallback, 40KB middle truncation, AbortError→message-or-interrupt-constant).FallbackToolUseErrorMessageintoformatToolResult: verbatimextractTag(utils/messages.ts:635), sandbox-violation/<error>-tag stripping, non-verboseInputValidationError→Invalid tool parameters, plus each tool'srenderToolUseErrorMessagecollapse (File not found,Error reading file,File must be read first,Error searching files,Error writing file,Error editing notebook) and the pluralized overflow hint.role=tool/function_call_output/FunctionResponsehave nois_errorfield; our converters never consumed it, so the tags were the only failure marker those models got. All three (chat-completions, Responses, Gemini) now prefixError:onis_errorresults, mirroringconvertToolResultContent(openaiShim.ts:309). Load-bearing for the de-tagging.EISDIR: illegal operation on a directory, read '<path>'. Other non-files (FIFOs/sockets) keep the generic refusal (TS streams them; deliberate pre-existing divergence).Verification
tests/test_tool_error_format_parity.py)tsc --noEmitcleanNon-gating follow-up notes (from review)
tool_execution.py:520abort-path content pre-bakesError:→ double prefix on OpenAI-family wires for resumed-session aborts (cosmetic; the clean fix is aligning that content withformatError, out of scope here)stripUnderlineAnsinot ported🤖 Generated with Claude Code