[BUG] Transient `api_error` mid-turn forks the transcript parentUuid chain — the retried reply is silently orphaned; later messages follow the error branch (surfaced by Rewind in Desktop)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Aug 3, 2026

Environment

  • Claude Code CLI: 2.1.220 (Claude Code) (native arm64 build), driven by Claude Desktop for macOS 1.24012.9
  • OS: macOS (Darwin 24.6.0)
  • Model: Fable 5 (Max thinking)
  • A Stop hook is configured (relevant: it produced the stop_hook_summary record below)
Privacy note: this report contains no conversation content — only structural evidence (record types, shortened uuids, timestamps). Session ID available on request.

Summary

When a turn hits transient API errors and the automatic retry then succeeds, the session .jsonl ends up with two sibling branches under the same user prompt: the system/api_error records on one branch, and the successful reply on the other. Subsequent writers (stop-hook record, the next user prompt) follow the error branch, so the assistant's reply is silently dropped from the canonical parent chain. The UI keeps displaying the reply, so the user notices nothing — until a Rewind re-renders from the canonical chain, at which point the errored turn (prompt + reply) vanishes entirely, looking like multi-message data loss.

Structural evidence (from the session .jsonl)

| record | uuid | parentUuid | timestamp (UTC) | note |
|---|---|---|---|---|
| assistant (assistant final text of turn N-1) | 26221c3f | 1466630b | 2026-08-02T17:41:02Z | last record of previous turn's reply |
| user (user prompt, turn N) | 5b8ecb9c | 26221c3f | 2026-08-03T01:44:20Z | fork point — has TWO child branches |
| assistant (assistant thinking) | d80278df | 5b8ecb9c | 2026-08-03T01:45:06Z | start of the successful retry's reply — child of the prompt |
| … 9 more records (assistant text/tool_use/tool_result) | | | 01:45:06→01:47:27 | reply streams normally on this branch |
| assistant (assistant final text of turn N) | 1ee59067 | e30303dc | 2026-08-03T01:47:27Z | end of the reply — this whole branch ends up orphaned |
| system/api_error (api_error #1) | 9b0a6d12 | 5b8ecb9c | 2026-08-03T01:44:30Z | also a child of the prompt — sibling of the reply branch |
| system/api_error (api_error #2) | dbc8f893 | 9b0a6d12 | 2026-08-03T01:44:40Z | chained after error #1 |
| system/stop_hook_summary (stop-hook summary) | 67f6fdcd | dbc8f893 | 2026-08-03T01:47:34Z | attaches to the ERROR branch, not the reply |
| user (user prompt, turn N+1) | 75933959 | 67f6fdcd | 2026-08-03T02:03:53Z | next user message follows the ERROR branch → turn N's reply is silently orphaned |

Resulting tree:

… turn N-1 reply (…17:41:02)
 └─ user prompt, turn N (01:44:20)              ← fork point
     ├─ ✘ branch A: full assistant reply, 11 records (01:45:06–01:47:27)   ← orphaned
     └─ ✔ branch B: api_error (01:44:30) → api_error (01:44:40)
              → stop_hook_summary (01:47:34)
              └─ user prompt, turn N+1 (02:03:53) → … current leaf

Expected

A single linear chain: the retried reply should extend the same lineage as the error records (or errors should be off-chain annotations), and the next user message must attach to the reply's leaf.

Actual

  1. Reply orphaned from the canonical chain at the moment turn N+1 was sent — so turn N+1's context (built from the parent chain) already excluded the orphaned reply, with no UI indication.
  2. Rewinding to the turn N+1 prompt re-renders from the canonical chain: turn N's prompt is now a dangling prompt whose only descendants are error records, and the Desktop folds it away — the user sees both the prompt and its reply disappear (multiple bubbles gone after a single-step rewind).
  3. Nothing is deleted on disk; the reply is recoverable by hand from the orphaned branch.

Repro (non-deterministic — needs a transient API failure)

  1. In Claude Desktop (macOS), send a prompt; have the API fail transiently (two api_error records ~10s apart), then the automatic retry succeed.
  2. Send another prompt after the reply finishes.
  3. Inspect the session .jsonl: the new prompt's parentUuid points at the stop_hook_summary/api_error lineage, not at the reply's last record.
  4. Rewind to the newer prompt → the errored turn and its reply vanish from the UI.

Related

Same family of parentUuid-chain corruption around error/retry paths: #43764, #59520, #21617, #27339; Desktop rewind dual-copy desync: #48955.

View original on GitHub ↗