Session becomes permanently un-resumable after usage-limit error (400 diagnostics.previous_message_id)
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
- 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. - Close the window or leave the process idle until the quota resets.
- Type any prompt (e.g.
continue). - Client returns 400
diagnostics.previous_message_id: must be the id from a prior /v1/messages response (starts with msg_). - All subsequent prompts fail identically.
--resumefrom 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)
- Find the session in
~/.claude/projects/<slug>/<sessionId>.jsonl. - Locate the last assistant entry whose
message.idstarts withmsg_. Note its line numberN. - Truncate the file to keep lines up to and including the matching
tool_resultuser turn that follows lineN(if any), and drop the fabricated assistant entry plus every subsequent line. - Relaunch and
--resumethe 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.