[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)

Status Open
Reported on v2.1.281
Maintainer reply None cached
Activity 0 comments · opened Sep 25, 2026

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)

  1. claude -p "Reply exactly: OK1" --session-id <uuid> (first-party auth).
  2. Append a user entry and an assistant entry to the session JSONL. Copy them from the real ones, and set message.id and requestId to gen-1790299999-TEST. This is what a gateway turn looks like on disk.
  3. claude -p "Reply exactly: OK3" --resume <uuid> returns API Error: 400 diagnostics.previous_message_id …, and it repeats on every retry.
  4. Remove requestId from assistant entries whose message.id does not start with msg_, and write the result as a copy under a new session id. --resume on 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).

View original on GitHub ↗