[BUG] --resume reconstructs a stale fork as the live conversation: an orphaned tool-permission grafted onto an old tool_use wins max-timestamp leaf selection (v2.1.150)

Resolved 💬 1 comment Opened May 25, 2026 by 0reo Closed Jun 26, 2026

Summary

On --resume, a long-running session was reconstructed from a stale early branch
instead of its actual latest thread — silently, no error. The parentUuid chain was
fully intact (no dangling references), so this is not a dropped-history or chain-break
problem — the failure is in leaf selection. An orphaned tool-permission, resolved at
startup, grafted a new branch onto a days-old tool_use, and that graft carried the newest
timestamps — so the loader selected it as the "live" conversation.

Environment

  • Claude Code v2.1.150, Linux, Opus 4.7 (1M context). Remote-control/bridge active.

Mechanism (verified against the session JSONL + minified cli)

  1. Resume selects the leaf with the latest timestamp, then walks parentUuid to root

(ve7S5H).

  1. The session contained an un-acted tool-permission from days earlier — a permission

prompt (a Skill invocation) that was never approved/denied; the user moved on and the
tool-call was abandoned (left in the tree as a dead branch).

  1. On resume after a restart, an **orphaned permission response** for that old tool_use's

id was drained at startup (handleOrphanedPermissionResponse + drainCommandQueue
prioritization) and written as a child of the ancient tool_use — grafting a fresh
branch onto it.

  1. That graft, plus everything typed afterward, became the newest-timestamped leaves.

Leaf selection therefore picked the graft branch and walked it to its old root,
reconstructing the wrong conversation and bypassing the actual latest thread. Nothing
was dropped — the real conversation was intact on disk, just on an unselected branch.

Why the v2.1.101 fix doesn't catch it

  • The v2.1.101 fix ("loader anchored on a dead-end branch instead of the live conversation")

is defeated here: the graft makes a stale-rooted branch carry the newest
timestamps, so the loader treats the dead fork as live.

  • v2.1.116 (dead-fork handling / silent-empty→error reporting) doesn't apply — the result

isn't an empty load; a valid but wrong branch is loaded.

Reproducibility

This is a mechanism report, not a deterministic repro. The triggering permission
response is not persisted in the JSONL (no control_response entries; the
bridge-session lastSequenceNum stays 0). The delivery path is almost certainly the
remote-control/cloud bridge re-delivering the un-acted permission on reconnect, but that is
inferred — only the grafted result is written to the transcript. The graft, its
parentage onto the old tool_use, and the timestamp ordering are all verifiable in the JSONL.

Suggested fix angles

  1. Drop orphaned permissions whose tool_use is not on the current live-leaf chain

(the "no matching tool_use" drop path already exists — extend it to "matching but
off-active-thread").

  1. Ensure dead-fork pruning runs before leaf selection, so a stale-rooted graft can't

win the max-timestamp race.

  1. Prefer the recorded session leaf (last-prompt / currentSessionLeafUuid) over a

leaf created by an orphaned-permission drain at startup.

— Written by Claude Code (Opus 4.7) on behalf of @0reo

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗