Tell a forked session apart from the one it came from - #53
Merged
Conversation
Claude Code forks a conversation by copying the transcript into a new file under a new session id and records nothing that says so. The two branches then sit in the list as unrelated rows carrying the same title, the same project and the same opening prompt, and resuming the wrong one silently drops everything that happened after the split. The only trace the format leaves is that copied messages keep the uuids they had in the original, so a shared first-message uuid is the fingerprint. Which branch is the original is decided at the message where two branches stop agreeing: whichever carried on first is the one that was there to be copied. Length cannot be the signal, because an original that was forked from and then abandoned is the shorter of the two. Detection runs at index time over the roots build_index now carries in index.json, and only reads the handful of files that share a fingerprint. Against 632 local sessions that is 0.16s of root scanning on a cold cache, nothing on a warm one, and it finds three forks.
Trailing the title, the mark needed a terminal over 200 columns wide to render: the list pane is 42% of the terminal, the row's fixed prefix is 42 columns, and a fork carries the same long title as the session it came from, so the title was already being cut off well before the mark. The screenshot in this PR only showed it because the recording was 260 columns. It now leads the title. Every title starts in the same column so the marks still line up to be scanned, and rows that are not forks pay nothing. A word rather than a glyph because ⑂ and ⋔ are both unreadable at 14px.
The list leads the title with the mark; the preview carried the same fact last on a line that opens with the project and the date. Same fact, different place in each view, and the wrong end of the line for a flag. The clause naming the original and the split point stays where it is, under the flag. Also drops a comment claiming HEADER_LINES constrains the preview header to five lines. Nothing computes from HEADER_LINES any more.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Forked sessions were invisible. Claude Code forks a conversation by copying the transcript into a new file under a new session id and records nothing that says so, so the two branches sat in the list as unrelated rows with the same title, the same project and the same opening prompt. Picking the wrong one resumes a branch missing everything that happened after the split.
The list now marks the fork, and the preview and
readheaders name the branch it came from and the message it split at.How it detects one. Copied messages keep the uuids they had in the original, which is the only trace the format leaves. Sessions whose first message shares a uuid are one conversation branched in two. Direction is decided at the message where two branches stop agreeing: whichever carried on first is the one that was there to be copied. Length cannot be the signal, because an original that was forked from and then abandoned is the shorter of the two.
Where the mark sits. Ahead of the title, in the list and in the preview and
readheaders alike. The list pane is 42% of the terminal and the row's fixed prefix is 42 columns, so a mark trailing the title needed a terminal over 200 columns wide to render at all, and a fork carries the same long title as the session it came from, which is exactly the row whose title gets cut. Leading the title costs nothing on rows that are not forks, and every title still starts in the same column so the marks line up to be scanned. A word rather than a glyph:⑂and⋔are both unreadable at 14px.Cost.
build_indexnow stores each session's first-message uuid inindex.jsonand carries it across cache hits, since it never changes. That is one extra partial read per new transcript, and only the handful of sessions sharing a fingerprint are read in full. Against 632 local sessions: 0.16s on a cold cache, nothing on a warm one, three forks found.Scope. Claude Code only; Codex has no equivalent. Ranking is untouched, so a fork and its original still compete as two results. Whether one should suppress the other is a separate question and needs real usage first.
Eight detection cases and four display cases in
tests/test_fork_detect.py, including fork-of-a-fork attribution, the abandoned-original case, and a regression test that the mark stays ahead of the title. Full suite: 112 pass.On the screenshot. Recorded against the synthetic demo corpus at a 160 column terminal, not a real transcript, with a fork fixture added to the generator.
docs/fork.pngis a separate commit so it can be dropped before merge if you would rather the repo not carry it.