[BUG] Session unrecoverable after upstream 429: every retry returns 400 previous_message_id
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?
When the Anthropic API returns a transient server-side rate-limit (Server is),
temporarily limiting requests (not your usage limit) · Rate limited
Claude Code's subsequent retry attempts fail permanently with:
API Error: 400 diagnostics.previous_message_id: must be the id from a prior
/v1/messages response (starts with msg_)
Every user-initiated retry (continue, resume, free-text prompts, even
/something) returns the same 400. The session becomes unrecoverable; the
only escape is /exit and starting fresh, which loses in-session state.
What Should Happen?
the session should resume after API rate limiting expired
Error Messages/Logs
pass. Thanks for the standard + sweep verdict, made shipping #68 straightforward.
⎿ API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited
✻ Crunched for 3s · 4 shells still running
❯ continue
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Churned for 0s · 4 shells still running
❯ continue
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Churned for 0s · 4 shells still running
❯ continue
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Crunched for 1s · 4 shells still running
❯ resume
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Worked for 0s · 4 shells still running
❯ what is wrong?
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Worked for 0s · 4 shells still running
❯ check out the history
⎿ API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with
`msg_`)
✻ Sautéed for 0s · 4 shells still running
Steps to Reproduce
- Hold a long-running interactive session.
- Issue a prompt that the API throttles with the "Server is temporarily
limiting requests (not your usage limit)" 429 (we hit it under normal
foreground use — no abuse, no usage cap).
- Observe the assistant turn does not complete.
- Type anything to retry.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.81
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
claude write-up
Title
Session unrecoverable after upstream 429: every retry returns 400 previous_message_id
Body
## Summary
When the Anthropic API returns a transient server-side rate-limit (Server is),
temporarily limiting requests (not your usage limit) · Rate limited
Claude Code's subsequent retry attempts fail permanently with:
API Error: 400 diagnostics.previous_message_id: must be the id from a prior
/v1/messages response (starts with msg_)
Every user-initiated retry (continue, resume, free-text prompts, even
/something) returns the same 400. The session becomes unrecoverable; the
only escape is /exit and starting fresh, which loses in-session state.
## Reproduction
- Hold a long-running interactive session.
- Issue a prompt that the API throttles with the "Server is temporarily
limiting requests (not your usage limit)" 429 (we hit it under normal
foreground use — no abuse, no usage cap).
- Observe the assistant turn does not complete.
- Type anything to retry.
Expected: client retries with the last valid previous_message_id (or no
previous_message_id if the failed turn was never persisted server-side),
the next call succeeds once the throttle clears.
Actual: every subsequent request includes a previous_message_id the API
doesn't recognize and returns a 400. The state never self-heals, regardless
of how long the user waits.
## Hypothesis
The client appears to advance its previous_message_id pointer optimistically
based on the request it sent, not based on a successful response containing
a real msg_… id. When the request fails before producing an assistant
message id, the pointer is left referencing something the server never
issued, and all subsequent calls are rejected.
## Severity
Medium: the workaround (/exit, lose context) is reliable but expensive —
in our setup the session held substantial in-flight context and
inter-agent state. The bug turns a transient server-side blip into a
permanent loss of the session.
## Version
(fill in: output of claude --version and OS — Linux 6.17.0 / Ubuntu in
my case)
## Full transcript of the failure mode
[Paste the literal terminal output from the failed session — the
"continue / resume / what is wrong? / check out the history" sequence
that all returned the same 400.]
9 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
The protocol-level shape of this is worth flagging — it's the same class of bug as keeping a foreign-key constraint pointed at a row that never committed.
When the API returns the transient
Server is temporarily limiting requests429, the assistant turn doesn't complete, so the server never emits anid: msg_…for that turn. The CLI's local session state, however, has already advanced to "expecting to attach the next user message toprevious_message_id = <placeholder for the in-flight turn>". From that point on, every subsequent request carries aprevious_message_idthat the server cannot resolve to a real prior response, and the API correctly returns 400 with themust be the id from a prior /v1/messages responsediagnostic.The recovery path on the client is to rewind the conversation pointer to the last successfully completed assistant turn when an error short-circuits the current turn before a
message_stopevent lands. Concretely:last_successful_message_idseparately frompending_previous_message_id.message_stop), discardpending_previous_message_idand reset tolast_successful_message_id.continue.Two extra wrinkles that the fix should cover:
Retry-Afterand 429 from sustained-usage limits (CHANGELOG entries 819 / 891 / 951 / 997 / 1383) — both should follow the same rewind path. Today the symptom is the same regardless of which 429 fired.tool_resultreferencing atool_use_idthe server doesn't recognize.The reason
/exitfollowed byclaude --resumeworks is exactly because it bootstrapsprevious_message_idfrom disk (where only completed turns were persisted), bypassing the in-memory stale pointer.Happy to dig into the repro further if it helps — I can reproduce the 400 chain by force-cancelling a
messages.streammid-stream against the API directly, which mirrors the 429 path without needing to wait for a real rate-limit.I am having the same issue.
/rewindto the last message that did not fail fixed it for me.Hit this on macOS with Claude Code 2.1.133. Triggered by a 529 Overloaded
mid-turn (not a 429, but same failure mode — server-side error before the
API issued a msg_... id). Every subsequent send returned the 400, including
fresh prompts and /resume.
Recovered by truncating the JSONL tail. The session file at
~/.claude/projects/<slug>/<uuid>.jsonl had 29 trailing entries after the
last real "id":"msg_..." line — a mix of two synthetic assistant entries
(model: "<synthetic>", UUID-style id) plus queue-operation, last-prompt,
ai-title, and file-history-snapshot records. Cutting back to the last
msg_... line let /resume latch onto a valid previous_message_id and the
session resumed cleanly with full context.
So this reproduces from 529 as well as 429 — looks like any upstream error
that kills the turn before an assistant msg_ id is issued leaves the
pointer dangling.
This happened to me via the web - I ended up having to resume/teleport the chat into Claude CLI and use /rewind like another user suggested.
GitHub Comment Draft — for posting on #58427 and #59520
---
+1 hit this in production · session became unrecoverable after a 529 cascade fired during an autonomous loop wakeup. After surgical recovery, sharing one finding worth knowing for whoever ships the upstream fix.
The naive truncation rule is wrong
Several existing community recovery scripts suggest "cut at the last
msg_entry." That matches mid-turn entries (thinking blocks, tool_use calls, multi-part responses) whosestop_reasonis"tool_use", not a turn-ending value. Cutting there leaves the session resumed mid-task — which itself causes weird state on--resume.The correct rule
Cut at the last assistant entry where all three hold:
type === "assistant"idstarts withmsg_stop_reasonis one of"end_turn"or"stop_sequence"Empirical evidence
In one corrupted 8256-line session, the naive rule suggested line 8229 (an
api_errorlog entry) or line 8230 (the first synthetic placeholder). Both cuts would have restarted the session mid-turn. The correct rule identified line 8211 — the last cleanend_turnboundary — which produced a perfectly clean restart.One-liner (grep-style)
Stricter awk (requires both predicates on the same line, JSON-key-order-agnostic)
Sibling note: detector should gate on more than 529
The same cascade pattern surfaces from 429, 503, 504, and 529 — not just 529. A detector worth its salt should also flag any
400response whose body carriesdiagnostics.previous_message_id, since that's the smoking gun that the bug has already fired and the session needs cut-point recovery.Detection regexes that work in production:
Portable recovery skill
We've packaged the full procedure as a Claude Code skill that works cross-platform (bash for Linux/Mac, PowerShell for Windows) and supports both local and SSH-remote broken sessions. It's available here: https://github.com/RickyPOnline/claude-code-recover-session
The skill includes:
find_last_clean_line()algorithm$$-self-exclusion safetyClosing
Filing in case it helps the upstream patch land correctly. Happy to provide more detail or test patches — we've reproduced the bug deterministically and have smoke-test fixtures available.
— Co-authored by Marina (Wave VPS-side, autonomous detector daemon) and Ccode (laptop-side Claude Code recovery skill), via Ricky.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
我遇到這問題時,直接ESC两次,然后選擇到没出問題前的記録,LUCK~