[BUG] 400 diagnostics.previous_message_id bricks a session after switching from an ANTHROPIC_BASE_URL gateway back to first-party (anchor accepts any requestId)
Summary
A session can have turns served through a gateway (ANTHROPIC_BASE_URL, e.g. OpenRouter's Anthropic-compatible endpoint). If it is then continued against the first-party API, every prompt fails with:
API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)
The session never recovers. Each failure appends a synthetic API-error entry, and anchor selection skips those entries, so the same invalid anchor is sent on every retry.
Root cause (2.1.281 bundle)
The anchor for diagnostics.previous_message_id is picked by walking the transcript backwards. It takes the first assistant entry that passes this check:
var ofo="msg_";function Fxe(e){if(e.isApiErrorMessage)return!1;if(e.requestId)return!0;if(e.isVirtual)return!1;let n=e.message?.id;return typeof n==="string"&&n.startsWith(ofo)&&e.message?.model!==lc}
if(e.requestId)return!0 runs before the msg_ prefix check. Gateway responses carry a request id of their own (OpenRouter: gen-…). An entry with message.id: "gen-…" therefore qualifies, and its id is sent as previous_message_id. Nothing breaks while the session runs through the gateway. The 400 appears on the first first-party request.
Reproduction (minimal, no gateway needed)
claude -p "Reply exactly: OK1" --session-id <uuid>(first-party auth).- Append a user entry and an assistant entry to the session JSONL. Copy them from the real ones, and set
message.idandrequestIdtogen-1790299999-TEST. This is what a gateway turn looks like on disk. claude -p "Reply exactly: OK3" --resume <uuid>returnsAPI Error: 400 diagnostics.previous_message_id …, and it repeats on every retry.- Remove
requestIdfrom assistant entries whosemessage.iddoes not start withmsg_, and write the result as a copy under a new session id.--resumeon the copy succeeds, and the model still has the full history, including the gateway turn.
Verified 2026-09-25 on 2.1.281 (the VS Code extension's native binary), macOS, first-party OAuth. A real session hit this after switching from an OpenRouter model back to a first-party model mid-session.
Suggested fix
Check the id before trusting requestId. For example, accept requestId only when message.id starts with msg_. Alternatively, the error is already classified as previous_message_id_invalid: on that error, drop the field and retry once.
Related
- #96033: same 400 after a connection error with a UUID id (open).
- #58427, #59520, #76008, #73776: same 400 with other triggers (closed).