Skip to content

fix(tools): thrown tool errors match original wire+display format#705

Merged
ericleepi314 merged 1 commit into
mainfrom
worktree-fix-tool-error-format-parity
Jul 13, 2026
Merged

fix(tools): thrown tool errors match original wire+display format#705
ericleepi314 merged 1 commit into
mainfrom
worktree-fix-tool-error-format-parity

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

A Read of a directory printed raw wire markup in the transcript:

⏺ Read(src)
  ⎿  Error: <tool_use_error>path is not a file: /Users/…/obama-blog/src</tool_use_error>

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:

  1. Wire parity — thrown call() errors are sent RAW in the original: content = formatError(error) with is_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_error is now a faithful formatError port (stderr/stdout parts, Command failed with no output fallback, 40KB middle truncation, AbortError→message-or-interrupt-constant).
  2. Display parity — ported FallbackToolUseErrorMessage into formatToolResult: verbatim extractTag (utils/messages.ts:635), sandbox-violation/<error>-tag stripping, non-verbose InputValidationErrorInvalid tool parameters, plus each tool's renderToolUseErrorMessage collapse (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.
  3. Error signal on OpenAI-family wiresrole=tool / function_call_output / FunctionResponse have no is_error field; our converters never consumed it, so the tags were the only failure marker those models got. All three (chat-completions, Responses, Gemini) now prefix Error: on is_error results, mirroring convertToolResultContent (openaiShim.ts:309). Load-bearing for the de-tagging.
  4. Read message parity — directory reads raise the byte-exact TS message (readFileInRange.ts:89): 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

  • pytest: 8529 passed / 0 failed (15 new in tests/test_tool_error_format_parity.py)
  • vitest: +8 passed; failures byte-identical to main's pre-existing baseline; tsc --noEmit clean
  • Critic review: APPROVE — every port verified against the TS source; adjacent converter+loop-guard suites re-run (170/170); independent JS harness of the ported display logic 25/25

Non-gating follow-up notes (from review)

  • tool_execution.py:520 abort-path content pre-bakes Error: → double prefix on OpenAI-family wires for resumed-session aborts (cosmetic; the clean fix is aligning that content with formatError, out of scope here)
  • ctrl+o expanded view shows model-facing raw bytes for the still-tagged class (existing TUI convention vs TS verbose stripping); stripUnderlineAnsi not ported

🤖 Generated with Claude Code

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>
@ericleepi314 ericleepi314 merged commit 710bf6e into main Jul 13, 2026
2 checks passed
@ericleepi314 ericleepi314 deleted the worktree-fix-tool-error-format-parity branch July 13, 2026 00:38
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