API-error retry bookkeeping orphans the successful turn from the parentUuid chain; --resume then silently drops it from context
Environment
- Claude Code CLI 2.1.170, Linux (RHEL 8)
- Session JSONL produced by Claude Code on 2026-06-10
Summary
When an API call fails mid-turn (e.g. a 502) and the automatic retry succeeds, the system/api_error bookkeeping row is not written at error time — it is buffered and flushed at the next user turn, and that next user row is parented to the api_error row instead of the real conversation tip. This orphans the entire successful retry output (the turn the user just read) onto a childless parentUuid branch.
claude --resume rebuilds model context by walking parentUuid from the tip, so after a resume the session has permanently lost that turn: the assistant cannot recall work it completed and the user read. Verified end-to-end below.
Evidence (session JSONL, 0-based lines, times UTC)
| line | type | uuid → parentUuid | timestamp |
|---|---|---|---|
| 183 | user | 746766dd → 954c3b08 | 00:28:26.196 |
| 184 | attachment (date_change) | 2bfe3cdd → 746766dd | 00:28:26.196 |
| 185–198 | assistant/user (full turn: thinking, tool_use/tool_result pairs, final text) | 7fbf8c93 → 2bfe3cdd, … chain … → 379dabdb | 00:28:56 – 00:29:28 |
| 199–200 | queue-operation enqueue/dequeue | — | 00:29:38.370 |
| 201 | system/api_error (retryAttempt: 1, Cloudflare 502) | 175c3fca → 2bfe3cdd | 00:28:26.415 (out of file order) |
| 202 | user | 95157e48 → 175c3fca | 00:29:38.373 |
Reconstruction:
- 00:28:26.196 — user turn delivered; the
attachmentrow (184) is the leaf when the API call starts. - 00:28:26.415 — attempt 1 fails with a 502. The api_error row captures this timestamp and parent (= the leaf at error time), but is not written to the file yet.
- 00:28:56–00:29:28 — the retry succeeds; the full turn output is appended normally, chaining from 184. Tip on disk: line 198.
- 00:29:38 — next user message: the buffered api_error row is finally flushed (line 201, error-time timestamp, hence out of order), and the new user row (202) is parented to it, not to the tip at 198.
- Everything afterward descends from the api_error row. Lines 185–198 have no descendants — a "dead branch" that is actually completed, displayed conversation.
Demonstrated consequence: resume context loss
Probe: claude -p --resume <session> --fork-session with all retrieval tools disallowed (--disallowedTools Bash Read Grep Glob ...), asked three questions:
- two facts that exist only in the orphaned segment (the opening words of an assistant status message there, and the exact temp-file path used in a command there);
- one control fact from the live branch seconds later (same vintage).
Result: the control came back verbatim-accurate, and the model also quoted exact mechanical details (temp-file paths) from other live turns of the same age and depth — but both orphaned-segment facts were reported absent, with the model itself noting the amend turn "is exactly the turn that's missing from my context." Token usage shows ~367k input tokens loaded into a 1M-context model, so this is not compaction or truncation: the segment is excluded by the chain walk.
Before the resume, the in-process model clearly had the content (the conversation continued from it normally). It is only lost when context is rebuilt from the file.
Expected behavior
- The next user turn after a retried-and-succeeded API call should be parented to the real conversation tip, not to the deferred error bookkeeping row.
- Alternatively/additionally, resume's chain walk should not drop a completed sibling branch that the error row orphaned.
Impact
--resume(and presumably fork/compact flows that walkparentUuid) silently lose completed work after any mid-turn API error + successful retry — a common pattern during provider incidents.- Transcript consumers that follow
parentUuidsee the successful turn as an abandoned dead branch.
Happy to share the full session JSONL or the probe transcript on request.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗