[BUG] claude --resume picks a stale branch on forked session JSONL, compounds on every resume on 2.1.108
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
On a session JSONL that has forked into multiple branches (e.g. due to concurrent claude --resume processes on the same
sessionId, or auto-continue tooling writing to old parents), claude --resume <sessionId> consistently anchors to a stale
branch — even when a much newer / much longer branch exists. Each subsequent resume extends the stale branch further,
making recovery progressively harder. The picker's choice doesn't match timestamps, chain length, or the JSONL's own
last-prompt metadata entry.
Environment
- Claude Code version: 2.1.108
- OS: Linux (Arch)
- Shell: bash
Actual behavior
On a real session (f3c03304-... in our repo) with:
- 3356 entries / 375 leaves in the JSONL tree
- Main conversation spine: chain length 1243, tip ts 2026-04-15T02:34:56Z, leaf uuid 93bb8cd9-...
- Most-recent meaningful branch: chain length 395, tip ts 2026-04-15T03:00:35Z, leaf uuid 88e505f0-...
- last-prompt metadata entry: {"type":"last-prompt","lastPrompt":"you fix it.","sessionId":"f3c03304-..."} — points to a
user message at ts 2026-04-14T04:57:20Z
claude --resume f3c03304-... consistently picks leaf 4633e2a1-...:
- chain length: 256
- tip ts: 2026-04-15T03:35:15Z (the timestamp is recent but it's just an auto-continue ping)
- walking root→leaf, substantive conversation ends at 2026-04-13T05:49 — two days old
- the leaf is a descendant of a 2026-04-13 user message "continue, had an interuption"
So the picker ignores:
- the 1243-node main spine (much longer chain, similar tip ts)
- the 395-node meaningful branch (newer tip)
- the last-prompt entry (points to yet a third node from 04-14)
The user sees 2-day-old content on resume. Every message they type gets appended as a child of that stale leaf, creating
new short forks off the old anchor. Re-resuming the session causes the fragmentation to compound — after several cycles
the tree accumulates hundreds of short branches, none connected to the real conversation tip.
Expected behavior
claude --resume <sessionId> should resume to the leaf of the branch representing the user's actual conversation tip —
i.e. either (a) the longest root-to-leaf chain, (b) the leaf with the latest substantive (non-auto-continue) user
message, or (c) a branch pointer that's reliably kept in sync when the user sends new messages. Whatever algorithm is
chosen should be deterministic and robust to concurrent writes.
Reproduction ingredients (not all needed — any combination that produces a forked tree seems sufficient):
- Long session that has passed at least one auto-compaction boundary.
- Multiple claude --resume <sameSessionId> processes running concurrently (this was accidentally triggered by a
workspace-restore feature in a third-party GUI app — 12 concurrent CLIs resuming the same session).
- External tooling (e.g. a /loop watcher, keep-alive prompt, or workflow script) that sends a user message to a resumed
session when the real user is idle.
- Any of the above interleaving with real user messages on a different branch.
Evidence / diagnostic
- JSONL path: ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl
- 375 leaves in the tree, with a clear main spine
- The three pointers (last-prompt, the picker's chosen leaf, the human-meaningful tip) all disagree
- Once the picker is anchored wrong, every resume amplifies the problem
Fix directions (suggestions)
- Make the leaf picker prefer chain length from root over timestamp, or combine the two (e.g., latest tip among leaves
with chain length ≥ some fraction of the max).
- Ensure there's exactly one writable CLI per sessionId at a time (file lock), or add a conflict-resolution pass on
resume that repairs the tree.
- Keep last-prompt (or an equivalent leaf pointer) updated atomically after each user input, and use it as the canonical
resume anchor.
- Expose a --resume-leaf <uuid> flag so users can manually specify which leaf to resume from when auto-picking gets it
wrong.
Current workaround
Manually rewrite the JSONL to contain only the desired linear chain (backing up the original), so the picker has no
branches to disambiguate. Not pleasant, and non-obvious.
What Should Happen?
.
Error Messages/Logs
Steps to Reproduce
.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.108
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Xterm
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗