[BUG] Session becomes permanently unresumable (400 `server_tool_use.id` pattern) after switching a session from a third-party provider back to the Anthropic API
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?
If a session is run under a third-party Anthropic-compatible provider (via a proxy that swaps ANTHROPIC_BASE_URL/auth) and then switched back to the real Anthropic API and resumed, the session can become permanently unresumable.
The provider I used (Z.AI / GLM) persisted an assistant turn containing a server_tool_use block whose id is in OpenAI style (call_...) instead of Anthropic's srvtoolu_.... On resume, Claude Code replays the full transcript to the Anthropic Messages API, which rejects the whole request with a 400:
messages.396.content.3.server_tool_use.id: String should match pattern '^srvtoolu_[a-zA-Z0-9_]+$'
Because the entire transcript is re-sent on every turn, the 400 repeats for every subsequent message. The session is bricked — there is no in-product way to recover; I had to hand-edit the session .jsonl.
Two distinct defects are involved in the same persisted turn:
- Foreign
server_tool_use.id. The Anthropic API enforces^srvtoolu_[a-zA-Z0-9_]+$forserver_tool_use.id; the persisted id has acall_prefix. (Note: clienttool_useids in the same transcript are alsocall_...and are accepted fine — the strict pattern only applies toserver_tool_use.) tool_resultinside an assistant-role message. The provider split one logical turn into three consecutiverole: assistantrecords: aserver_tool_use, a text summary, and atool_resultthat references the same id. Atool_resultblock inside an assistant message is invalid on the Anthropic API, so fixing only defect #1 surfaces a second400.
The broader robustness gap is provider-agnostic: a single malformed historical block makes an entire session unresumable with a hard 400 loop and no recovery path.
What Should Happen?
Switching a session back to the Anthropic API and resuming should not hard-fail. Options, roughly in order of preference:
- On resume/replay, normalize or drop tool blocks that can't be sent to the active endpoint (e.g. foreign-prefixed
server_tool_useids,tool_resultblocks in assistant-role messages), rather than forwarding them verbatim. - Detect a mismatch between the persisted transcript's provider and the active endpoint and surface a recoverable, actionable error instead of a raw API
400. - Provide a repair path (e.g.
claude --resumeself-heals, or a documented transcript-sanitize command).
Error Messages/Logs
# Weekly limit on the third-party provider is what prompted switching back to Anthropic:
API Error: Request rejected (429) · [1310][Weekly/Monthly Limit Exhausted...]
# After `ccs anthropic` + resume, every message attempt returns:
API Error: 400 messages.396.content.3.server_tool_use.id: String should match pattern '^srvtoolu_[a-zA-Z0-9_]+$'
Steps to Reproduce
- Run a Claude Code session under a third-party Anthropic-compatible provider (here Z.AI / GLM, reached by pointing the base URL + auth at the provider). During the session, trigger a provider-specific server tool (in my case an image-analysis tool named
analyze_image). - The provider persists an assistant turn into the session
.jsonlcontaining aserver_tool_useblock with an id likecall_888211ac23974faf8935a8fb, plus a pairedtool_resultblock inside an assistant-role record. - Switch the session back to the real Anthropic API (I use a small
ccs anthropichelper that swaps the provider config) and resume the same session. - Send any message. The client replays the full transcript and the Anthropic API returns
400 ... server_tool_use.id: String should match pattern '^srvtoolu_[a-zA-Z0-9_]+$'. - Every subsequent message returns the same
400. The session cannot be resumed.
Minimal shape of the offending persisted record (one .jsonl line, trimmed):
{"type":"assistant","message":{"role":"assistant","content":[
{"type":"server_tool_use","id":"call_888211ac23974faf8935a8fb","name":"analyze_image","input":{}}
]}}
...followed a couple of lines later by, also under role: assistant:
{"type":"assistant","message":{"role":"assistant","content":[
{"type":"tool_result","tool_use_id":"call_888211ac23974faf8935a8fb","content":"..."}
]}}
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.206 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
VS Code integrated terminal
Additional Information
Manual workaround that unblocked me (edit the session .jsonl under ~/.claude/projects/<slug>/):
- Find offending blocks — any
server_tool_use.idnot matching^srvtoolu_, and anytool_resultin arole: assistantmessage. - Convert both blocks to plain
textblocks, preserving their content (the human-readable tool output is already present as text, so no information is lost). - This clears both the strict-id rejection and the misplaced-
tool_resultrejection in one pass; the session resumes normally.
Notes for triage:
- A third-party provider (Z.AI / GLM), reached via a base-URL/auth proxy, was in the loop for part of the session. That setup is outside Anthropic's supported providers, so the specific foreign
idshape originates there. I'm reporting it because Claude Code persists provider output verbatim and then forwards it to a different endpoint on resume, and because a single bad historical block bricking a whole session with no recovery path seems worth hardening against regardless of provider. - In my ~1,200-line transcript there was exactly one such offending pair; the hundreds of ordinary client
tool_use/tool_resultpairs withcall_ids replayed fine, which is what isolates the failure toserver_tool_usespecifically.