[BUG] Session permanently dead-locks with "400 Tool reference 'WaitForMcpServers' not found" after an early tool search during MCP startup
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
A session becomes permanently unusable: every user message returns the same API error and no assistant turn can complete.
API Error: 400 Tool reference 'WaitForMcpServers' not found in available tools
The failure is not recoverable from inside the session — continue, /doctor, and plain retries all reproduce the identical 400 instantly, because each request re-sends the same poisoned conversation history. The only escape is /clear (loses all context) or hand-editing the transcript JSONL.
Root cause, traced from the session transcript:
- Early in the session — while MCP servers were still connecting — the assistant issued a server-side deferred-tool search (
tool_search_tool_regex). The resultingtool_search_tool_resultwas written into the transcript with a snapshot of the tool catalog as a list oftool_referenceentries. That snapshot includedWaitForMcpServers. WaitForMcpServersis a transient startup tool — it is only advertised in the request'stoolswhile MCP servers are initializing, then dropped once they finish connecting.- A
tool_search_tool_resultis immutable transcript history. On every subsequent request the API re-validates the entire conversation, finds atool_referencetoWaitForMcpServersthat is no longer in the advertised tool set, and rejects the whole request with a 400. The session is poisoned for good.
The trigger is a race: the tool search must land inside the MCP-connect window for the transient tool to be captured. This makes MCP-heavy configs (multiple MCP servers/plugins) the most exposed, since they lengthen that window and often prompt an early tool search.
What Should Happen?
A completed session must never become permanently un-loadable. Either:
- a
tool_search_tool_resultshould not surface transient/session-lifecycle tools (likeWaitForMcpServers) into a persisted result, or - on every request the harness should reconcile persisted
tool_references against the current tool set — re-advertising or stripping now-absent tools so the API never sees a dangling reference.
Error Messages/Logs
API Error: 400 Tool reference 'WaitForMcpServers' not found in available tools
Steps to Reproduce
- Configure the session with one or more MCP servers (via plugins or
.mcp.json) so MCP connection takes a moment at startup. - Start a session and, early on (while MCP servers are still connecting), take an action that causes a deferred-tool search — e.g. a task that makes the assistant call
tool_search_tool_regex. - Observe the transcript: the resulting
tool_search_tool_resultrecords atool_referenceslist that includesWaitForMcpServers(and, notably, the full catalog — entries not matching the search regex; e.g. apatternofbash|shell|execute|read|glob|grep|write|editreturnedAgent,Artifact,WaitForMcpServers, etc.). - Continue the session past MCP-connect completion (send any further message).
- Every subsequent turn fails with
400 Tool reference 'WaitForMcpServers' not found in available tools, permanently.
Confirming evidence in the JSONL (~/.claude/projects/<project>/<session-id>.jsonl): the tool_search_tool_result block's content.tool_references contains {"type":"tool_reference","tool_name":"WaitForMcpServers"}; the earliest turn containing it succeeds, and all later turns 400. Removing only that single reference from the block and resuming clears the error with full context intact — confirming that one dangling reference is the sole cause.
Environment
- Claude Model: Not sure / Multiple models
- Is this a regression?: I don't know
- Claude Code Version: 2.1.217 (Claude Code)
- Platform: Anthropic API
- Operating System: macOS
- Terminal/Shell: Terminal.app (macOS)
Additional Information
Workaround for an already-poisoned session (avoids losing context): with the session closed, edit its transcript JSONL to drop the WaitForMcpServers entry from the tool_search_tool_result's content.tool_references, then claude --resume <session-id>. /clear also clears it but discards conversation history.
Secondary observation worth checking: the tool search returned the entire tool catalog rather than only entries matching the supplied regex — surfacing transient tools that the query didn't ask for is what allowed the poison in.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Reproduced deterministically on Claude Code 2.1.220, macOS 27.0, with two MCP
servers configured (one HTTP, one stdio). Adding some detail that may narrow it,
plus a recovery that preserves the session.
It is deterministic per boot, not intermittent. Two consecutive fresh
sessions in the same window, same project, same two MCP servers, both ended up
carrying the poisoned block, and both capped their context window shortly after
start. The trigger is not a rare race for us: if a server-side tool search fires
while the MCP servers are still connecting, the block lands, every time.
The poisoned block is much larger than the one name in the error. The error
names a single tool, so the obvious repair is to remove that one. That is not
enough. In our transcript the snapshot spanned 5
tool_search_tool_resultblocks holding 64
tool_referenceentries, 37 built-in and 27 MCP tools. Thebuilt-in set included tools that are not in this client's advertised set at all:
plus MCP tools from a server that was no longer connected. So any of these can be
the tool that 400s, depending on which one the API validates first. We removed
WaitForMcpServers, resumed, and the next/compactfailed with400 Tool reference 'EndConversation' not found in available tools. It is aqueue of dangling references, not a single bad entry.
Nothing ever called these tools. We checked every transcript on the machine:
there is no
tool_useforWaitForMcpServersanywhere, in any session. Thereferences exist only inside the search-result snapshot, so they are a record of
what was advertised at that instant, not of anything the model did. That matters
for the fix: dropping them costs the conversation nothing.
Why the session cannot recover on its own.
/compactre-sends the fullhistory, which still contains the block, so compaction fails with the same 400
that every other request fails with. The window fills and there is no path back.
Ours reached 760k tokens on a 200k window before we intervened. Auto-compact
cannot save it either, for the same reason.
Recovery that keeps the conversation.
/clearworks but discards thesession. Instead, with the session closed, strip every
tool_referenceentryfrom the
tool_search_tool_resultblocks in~/.claude/projects/<project-dir>/<session-id>.jsonl, thenclaude --resume <session-id>. Strip all of them rather than the named one, forthe queue reason above. After doing that on a session that had been failing every
compact:
/compactran to completion instead of returning 400 instantlyPreCompactandPostCompacthooks both fired and completedLine count unchanged, every line still valid JSON. So the rest of the transcript
is undamaged by the removal, which further suggests the references are inert
history rather than load-bearing structure.
Suggested fix, from the outside. Either exclude transient startup tools from
the catalogue snapshot written into
tool_search_tool_result, or have the clientfilter
tool_referenceentries against the currently advertised tool set when itbuilds a request, rather than replaying the snapshot verbatim. The second also
covers references left behind when an MCP server disconnects mid-session, which
is the same failure with a different cause.
Happy to supply a redacted transcript fragment if that helps.
Thanks for the unusually detailed report — the transcript-level tracing made this much easier to test.
I tried to reproduce on 2.1.233 (Linux, Anthropic API) using a deliberately slow-connecting MCP server and forcing tool searches during the connect window:
So I can't reproduce the dead-lock on the current release — but I can't rule out the path you hit, since the search result you describe looks like one recorded by the API itself rather than by the local tool, and that variant isn't something I could trigger here.
Could you check whether it still happens on 2.1.233 or newer? If it does, please share the exact version, whether the session used a custom base URL or gateway, and the (redacted) shape of that block in the JSONL. Tagging
needs-reproin the meantime.🤖 Generated with Claude Code
We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.