claude-code - 💡(How to fix) Fix Resume of session with synthetic assistant tail (UUID message.id) sends invalid previous_message_id → API 400

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When a session JSONL ends with <synthetic> assistant entries (UUID-style message.id, not msg_...), /resume followed by any input fails with:

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

The resume path appears to pick the last assistant message.id from the JSONL as previous_message_id without filtering out synthetic entries.

Error Message

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

Root Cause

When a session JSONL ends with <synthetic> assistant entries (UUID-style message.id, not msg_...), /resume followed by any input fails with:

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

The resume path appears to pick the last assistant message.id from the JSONL as previous_message_id without filtering out synthetic entries.

Fix Action

Workaround

Truncate the JSONL to the last entry preceding the first synthetic assistant. After truncation /resume continues correctly with previous_message_id = msg_01MsrY....

Code Example

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

---

[465] type=assistant  msgid=msg_01MsrY...    ← last REAL assistant (has pending tool_use=Agent)
[466] type=last-prompt
[467] type=ai-title
[468] type=permission-mode
[469] type=user        content=[tool_result] ← real tool_result: "You've hit your limit · resets 12am (Asia/Shanghai)"
[470] type=assistant   model=<synthetic> msgid=d1e01673-...   ← synthetic begins
[472] type=user        text="continue"
[485] type=assistant   model=<synthetic> msgid=c7a85e6a-...
...
[552] type=assistant   model=<synthetic> msgid=cbcb88fd-...  ← last assistant in file, picked up as previous_message_id
RAW_BUFFERClick to expand / collapse

Summary

When a session JSONL ends with <synthetic> assistant entries (UUID-style message.id, not msg_...), /resume followed by any input fails with:

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

The resume path appears to pick the last assistant message.id from the JSONL as previous_message_id without filtering out synthetic entries.

Environment

  • Claude Code: 2.1.131
  • OS: Linux 6.18.2-zen2-gentoo-dist
  • Shell: zsh

Reproduction

  1. In a session, trigger a tool result that causes the harness to fail mid-stream — in my case a Task/Agent subagent returned "You've hit your limit · resets 12am (...)" (i.e. weekly-limit hit while a tool_use was already in flight).
  2. Press "continue" one or more times. The harness writes synthetic assistant turns with model: "<synthetic>" and a UUID message.id (e.g. cbcb88fd-b582-411d-bff0-843b821ac04f).
  3. Exit Claude Code. Wait until the limit resets.
  4. claude/resume → pick that session → type anything.
  5. Observe API Error: 400 diagnostics.previous_message_id: must be the id from a prior /v1/messages response (starts with msg_).

What the session JSONL looks like

Tail of the broken file (annotated):

[465] type=assistant  msgid=msg_01MsrY...    ← last REAL assistant (has pending tool_use=Agent)
[466] type=last-prompt
[467] type=ai-title
[468] type=permission-mode
[469] type=user        content=[tool_result] ← real tool_result: "You've hit your limit · resets 12am (Asia/Shanghai)"
[470] type=assistant   model=<synthetic> msgid=d1e01673-...   ← synthetic begins
[472] type=user        text="continue"
[485] type=assistant   model=<synthetic> msgid=c7a85e6a-...
...
[552] type=assistant   model=<synthetic> msgid=cbcb88fd-...  ← last assistant in file, picked up as previous_message_id

All four sessions in this project: only the one with a synthetic tail 400s on resume. The other three end on real msg_... IDs and resume cleanly.

Workaround

Truncate the JSONL to the last entry preceding the first synthetic assistant. After truncation /resume continues correctly with previous_message_id = msg_01MsrY....

Suggested fix

When computing previous_message_id on resume, skip entries where message.model == "<synthetic>" (or, equivalently, where message.id does not match ^msg_). Ideally also avoid persisting synthetic entries with a message.id field that overlaps the API ID namespace — give them a distinct shape so they cannot accidentally be sent as anchors on the next API call.

Related (different roots, similar shape)

  • #53260 (synthetic "No response requested." on auto-resume)
  • #58320 (synthetic PDF-validation literal persists across resume)
  • #55811 (closed — 429 mid-send leaves branch unrecoverable)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING