Session becomes permanently un-resumable after usage-limit error (400 diagnostics.previous_message_id)

Open 💬 0 comments Opened Jul 9, 2026 by matthieu-labs

Title: Session becomes permanently un-resumable after monthly-usage-limit error (400 diagnostics.previous_message_id on every subsequent request)

Claude Code version: 2.1.131
Platform: macOS 15.5 (darwin arm64), zsh
Auth: direct Anthropic API (Claude Max subscription), not Bedrock

Summary

When an assistant turn fails because the Anthropic account's monthly usage limit is exceeded, Claude Code records a synthetic assistant entry in the session JSONL with a locally-generated UUID as the id (e.g. 0fb9ad5f-2746-4a02-9c21-7c106aba2f40) instead of a real msg_… id. On any later attempt to continue the session, the client submits that UUID as diagnostics.previous_message_id, and the API rejects the whole request with:

API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)

Even after the usage window resets, the session stays permanently broken until the corrupted tail is manually trimmed from the JSONL file. The only in-product recovery is /clear, which discards all conversation state.

Reproducer

  1. Start a session and work until the account hits its monthly (or 5-hour rolling) usage limit. The last turn will render as You've hit your org's monthly usage limit.
  2. Close the window or leave the process idle until the quota resets.
  3. Type any prompt (e.g. continue).
  4. Client returns 400 diagnostics.previous_message_id: must be the id from a prior /v1/messages response (starts with msg_).
  5. All subsequent prompts fail identically. --resume from another window into the same session file reproduces the same 400 on first prompt.

Evidence from the session JSONL

Last real assistant entry (successful Write tool_use):

{"type":"assistant","message":{"id":"msg_011CcqF4W379Qgf82hgMoEuq", ...}}

Immediately after the tool_result, the synthetic entry recorded for the limit error:

{"type":"assistant","message":{"id":"0fb9ad5f-2746-4a02-9c21-7c106aba2f40","content":[{"type":"text","text":"You've hit your org's monthly usage limit"}], ...}}

Then the retry attempt after quota reset:

{"type":"assistant","message":{"id":"9d752fcf-a104-4a0c-b920-f60f702354cb","content":[{"type":"text","text":"API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)"}], ...}}

Both fake ids are UUIDs, not msg_…. The client is picking previous_message_id from the newest assistant entry regardless of whether it originated from a real /v1/messages response.

Suggested fix (client-side)

At the point where previous_message_id is populated for the outgoing request, walk backward through the transcript and skip any assistant entry whose id does not start with msg_ (i.e. locally-fabricated error placeholders). Use the id from the most recent real msg_… entry instead. Alternatively, omit the diagnostics.previous_message_id field entirely when no valid id can be found - the API accepts requests without it.

A second safeguard: when persisting an error-derived synthetic assistant entry, tag it (e.g. synthetic: true or use a distinct id prefix) so downstream consumers can filter it deterministically.

Manual workaround (for anyone hitting this now)

  1. Find the session in ~/.claude/projects/<slug>/<sessionId>.jsonl.
  2. Locate the last assistant entry whose message.id starts with msg_. Note its line number N.
  3. Truncate the file to keep lines up to and including the matching tool_result user turn that follows line N (if any), and drop the fabricated assistant entry plus every subsequent line.
  4. Relaunch and --resume the session.

Impact

  • Any session that hits a rate/usage limit becomes unrecoverable without touching disk.
  • Users lose the option to resume long research sessions after their quota window resets.
  • /clear -> full context loss is currently the only in-product mitigation.

View original on GitHub ↗