[BUG] Tool errors ("No such tool available") are sent to the model but never rendered in the TUI
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
When a tool_use block names a tool that does not exist, Claude Code returns a <tool_use_error> to the model — but renders nothing in the TUI. Successful tool calls render normally, so from the user's side the failed call simply never happened.
The information is asymmetric: the model knows the call failed; the user does not.
Reproduction
I isolated this from any provider by pointing Claude Code at a local mock Messages API server, so no real model is involved.
- Run a mock server implementing
POST /v1/messagesthat streams back atool_useblock naming a tool that does not exist, withstop_reason: "tool_use". - Start Claude Code against it:
ANTHROPIC_BASE_URL=http://localhost:8899 claude - Type anything once.
- Watch the TUI, and separately log the requests the mock receives.
Three cases ran in one session, in order:
| # | what the mock returns | what the model receives (from the mock's request log) | what the TUI shows |
|---|---|---|---|
| A | tool_use with name: "" | <tool_use_error>Error: No such tool available: </tool_use_error> | nothing |
| B | tool_use with name: "NoSuchToolXYZ" | <tool_use_error>Error: No such tool available: NoSuchToolXYZ</tool_use_error> | nothing |
| C | tool_use with name: "Bash" (valid) | the tool result, is_error: false | ● Bash(echo …) + ⎿ mock-scenario-C-ok — rendered correctly |
A and B produce no error row, no warning, and no indication of any kind. C renders normally.
An empty tool name is not required: B has a perfectly well-formed name and is equally invisible.
The failure is invisible in real time as well
In the run above the user typed once. Claude Code received the tool_use_error and — with no further user input — issued the next request on its own. A, B and C all executed inside that single user turn.
So the user does not even observe "something took a while": they see one input producing one apparently normal exchange. How many tool calls failed inside that turn cannot be recovered from the TUI.
Impact
This is not only a missing log line. It removes the user's ability to check what the model tells them.
In a real session before I built the mock, the model reported that two of its tool calls had failed and that it was retrying. The user's TUI showed no tool errors at all for those turns — only the successful retries that followed. On that evidence the user concluded the model was fabricating the failures. The model then retracted its report and recorded the episode as its own fabrication. The session JSONL shows the failed calls and their <tool_use_error> results were real.
I consider this a real harm rather than a cosmetic gap: an accurate report from the model was judged false because the only evidence available to the user had silently dropped the very events under discussion.
Related
- #77778 — same family (a failed tool call's row is dropped from the TUI while the data exists in the JSONL and in
tool_result), but a different failure: there the tool was executed and its result row disappears; here the tool is never executed and no error row appears at all. - #75629 — mentions an existing retry path for malformed tool calls (
"Your tool call was malformed and could not be parsed. Please retry."). That message never appeared in any of the cases above: atool_useblock naming an unknown tool is handled as "no such tool" and does not enter that path. - #83142 — the same asymmetry in the opposite direction: an API error that kills a turn is rendered to the user but leaves no trace for the model. Same underlying problem (the user and the model are shown different sets of failures), separate defect with a separate fix.
What Should Happen?
A tool call that fails should be visible to the user in whatever form fits the TUI — an error row under the call, a one-line notice, or a counter. The exact rendering matters less than the invariant:
If a failure is reported to the model, it should also be reported to the user.
Concretely, either of these would resolve it:
- Render
is_error: truetool results the way successful ones are rendered (the data is already in the transcript), or - Route unknown-tool errors into the existing malformed-tool-call path so they surface the way parse failures already do.
Error Messages/Logs
Requests received by the mock server (only tool_result blocks shown):
=== request #2 ===
tool_result[is_error] "<tool_use_error>Error: No such tool available: </tool_use_error>"
=== request #3 ===
tool_result[is_error] "<tool_use_error>Error: No such tool available: </tool_use_error>"
tool_result[is_error] "<tool_use_error>Error: No such tool available: NoSuchToolXYZ</tool_use_error>"
=== request #4 ===
tool_result[is_error] "<tool_use_error>Error: No such tool available: </tool_use_error>"
tool_result[is_error] "<tool_use_error>Error: No such tool available: NoSuchToolXYZ</tool_use_error>"
tool_result[ok] "mock-scenario-C-ok"
The TUI for the same session showed only the assistant's text for A and B,
and the Bash call plus its result for C. No error row appeared at any point.
Platform
Other (local mock Messages API server — the behaviour is in the client and does not depend on the provider)
Operating System
Ubuntu/Debian Linux
Terminal/Shell
iTerm2
Additional Information
Claude Code version: 2.1.220
The mock used for the reproduction is about 150 lines (FastAPI) and streams standard Anthropic SSE events (message_start → content_block_start/content_block_delta/content_block_stop → message_delta → message_stop). Happy to post it if that would help.
✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗