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)
Preflight
- Searched. Closest: #89820 (auto-injected continuation parented to a stale
last-promptleafUuid), #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 aparentUuidtree — 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-promptrecords, and none of them has aleafUuidequal to the fork parent20f04735. The stale parent did not come fromlast-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
- 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.
- 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.
- Failing both, persist an explicit tip pointer and prefer it over any client-supplied parent (the more robust fix #89640 also asks for).
Repro
- Open a session in the desktop app.
- Attach an external client to that same session over the bridge and post turns from it.
- Let the session sit idle past the 900s
WarmLifecycletimeout between posts. - Post again from the external client.
- Inspect the transcript: the new turn's
parentUuidcan 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.