Bridge/queue-delivered user turns are parented to a stale leaf, forking the transcript and silently stranding hours of work (3/3 forks in one session, distinct from #89820)

Status Open
Reported on v2.1.247
Maintainer reply None cached
Activity 0 comments · opened Sep 2, 2026

Preflight

  • Searched. Closest: #89820 (auto-injected continuation parented to a stale last-prompt leafUuid), #89640 (stale extension process flushes late tool_results onto an abandoned branch), #88597 (loader keeps one branch on resume). This is the same class — resume/tip selection on a parentUuid tree — but a different trigger and a different stale source: see "Why this is not #89820" below.
  • Single bug, one causal chain, evidenced from one transcript.
  • Claude Desktop (Windows/MSIX), engine 2.1.247.

What's wrong

A user turn that arrives through the session bridge (an external client posting into a running session, delivered via queue-operation enqueue/dequeue) is written with parentUuid pointing at a stale leaf rather than the conversation's live tip. The tree forks. Everything written on the resulting branch — real user turns and the work they produced — is off the branch the window renders and off the model's context. No error, no banner, nothing in main.log.

In the measured session this stranded 190 records containing 11 human turns, spanning 9.5 hours.

Evidence

Transcript ~/.claude/projects/<encoded-cwd>/b57a8c60-….jsonl — 3223 records, 10 leaves, 43 human turns, covering 2026-08-31T17:33:25Z → 2026-09-02T22:02:35Z.

The fork:

fork parent   line 3666  uuid 20f04735  assistant   2026-09-01T23:13:34Z
  ├ branch A  line 3667  assistant (tool_use)       2026-09-01T23:13:53Z
  │           424 records,  6 human turns,  grows to 2026-09-02T22:02:35Z   <- live
  └ branch B  line 3674  user "…"                   2026-09-02T01:23:01Z
              190 records, 11 human turns,  ends at 2026-09-02T10:53:08Z    <- stranded

Branch B was attached 2 hours 9 minutes after branch A had already continued from the same parent.

The five records immediately preceding the graft identify the delivery path:

line 3670  mode
line 3671  bridge-session                                     <-- external client attaches
line 3672  queue-operation  {"operation":"enqueue","content":"…"}
line 3673  queue-operation  {"operation":"dequeue"}
line 3674  user  parentUuid=20f04735  userType=external       <-- stale parent, 2h09m old
           isMeta=null  isSidechain=false                     <-- a real human turn

The signature is 3 out of 3

Every fork in this session is preceded by the identical record sequence:

| fork parent (UTC) | late child | preceding records |
|---|---|---|
| 2026-08-31T22:49:09 | line 3551 | agent-name > mode > bridge-session > queue-operation > queue-operation |
| 2026-09-01T23:13:34 | line 3674 | agent-name > mode > bridge-session > queue-operation > queue-operation |
| 2026-09-02T17:37:21 | line 4146 | agent-name > mode > bridge-session > queue-operation > queue-operation |

Turns typed directly in the desktop window produced zero forks in this session. The file holds 261 bridge-session and 82 queue-operation records, so the bridge path is heavily used and only some deliveries misparent — consistent with the stale value being a cache that is sometimes current.

Why this is not #89820

#89820's mechanism is the auto-injected isMeta continuation inheriting a stale last-prompt leafUuid. Both are absent here:

  • The grafted record is isMeta: null, userType: "external", with real user text — not a "Continue from where you left off." meta turn.
  • The file contains 246 last-prompt records, and none of them has a leafUuid equal to the fork parent 20f04735. The stale parent did not come from last-prompt.

It is also not #89640: the late records here are a user turn and its attachment chain, not error tool_results answering abandoned tool_use ids.

Likely proximate trigger

main.log shows WarmLifecycle disconnecting idle sessions on a 900-second timer throughout, e.g.:

2026-09-02 10:22:44 [WarmLifecycle:session] Idle timeout reached, disconnecting local_…
2026-09-02 10:22:48 [WarmLifecycle:session] Idle timeout reached, disconnecting local_…
2026-09-02 10:23:01  <- graft written (KST; = 01:23:01Z)

13–17 seconds separate a disconnect sweep from the misparented write. A bridge client posting into a session that has just been idle-disconnected is the shape that matters here, and it is far more frequent than an app restart: it can recur every 15 minutes of inactivity, which is exactly the cadence of a human conversing through an external client.

I am flagging this as a correlation, not a proven cause — the disconnect lines name other session ids, and I could not tie one to this session's local_… id from the log alone.

What should happen

  1. On delivering a queued/bridged turn, resolve the parent at dequeue time from the live tip, not from a value captured when the client attached.
  2. If the captured parent already has descendants, either adopt the newest leaf or refuse and surface it. Silently grafting onto a node that has moved on is the data-loss step.
  3. Failing both, persist an explicit tip pointer and prefer it over any client-supplied parent (the more robust fix #89640 also asks for).

Repro

  1. Open a session in the desktop app.
  2. Attach an external client to that same session over the bridge and post turns from it.
  3. Let the session sit idle past the 900s WarmLifecycle timeout between posts.
  4. Post again from the external client.
  5. Inspect the transcript: the new turn's parentUuid can point at a leaf that already has descendants; from then on the window and the model see only the other branch.

Notes on measurement

Timestamps in the transcript are UTC (Z); the desktop main.log is local time. Leaf and fork counts alone are misleading — an assistant node with a tool_use child and a tool_result child is a normal two-child node, not a conversation fork. The discriminator used above is two or more child branches that each contain at least one non-tool_result, non-isMeta, non-isSidechain user turn; a scanner using that rule found 56 such forks across 187 transcripts (1.45 GB) on this machine, and was validated against hand-built positive and negative fixtures before the numbers were trusted.

View original on GitHub ↗