Skip to content

csb chain-awareness: index parent_session_id from forkedFrom + csb show/chain commands #22

@djdarcy

Description

@djdarcy

Problem

Claude Code session JSONLs form fork chains. New session files are minted in two scenarios (verified empirically and by source reading):

  1. /branch slash command -- mints forkSessionId = randomUUID(), copies source messages with forkedFrom: { sessionId, messageUuid } into a new file (commands/branch/branch.ts:61-160 in claude-code source).
  2. --fork-session CLI flag on resume -- writes source messages into a new file using the startup-minted ID, with the same forkedFrom pointer on line 1 (utils/sessionRestore.ts:436-463).

Each fork's first line is a compact_boundary row carrying forkedFrom: { sessionId, messageUuid }. csb currently treats every UUID as an independent session. The chain relationships are invisible in csb list, csb show, and the SQLite index.

For long-running thread of work that has been forked multiple times, this means:

  • csb show <uuid> says nothing about what session this was forked from.
  • csb show <uuid> says nothing about whether any forks-of-this-session exist.
  • csb resume <uuid> (when it exists, per Alpha CLI launcher for claude-code-history-viewer -- csb view (Alpha #3) #14) cannot warn the user that they are resuming a session with active forks (which would duplicate post-fork content if compaction lands).
  • Reconstructing a full conversation arc across fork boundaries requires manually reading line 1 of each candidate file.

Real-world example

The AMD_INTIGRITI session investigation found a 4-deep chain in ~/.claude/projects/C--/:

e0679878 (72.4 MB, original)
   └── 23f58cd1 (1.7 MB, forked 2026-02-23)
         └── f2d0d074 (27.4 MB, forked same instant)
               └── dac78227 (3.1 MB, forked 2026-04-30)

csb list shows these as four independent rows. The fork relationships are invisible.

Proposed solution

Add chain-awareness to csb's index and display:

Schema additions

Add columns to the sessions table:

Column Type Source
parent_session_id TEXT NULL line 1's forkedFrom.sessionId if present
parent_message_uuid TEXT NULL line 1's forkedFrom.messageUuid if present
is_fork BOOLEAN computed: subtype === 'compact_boundary' && forkedFrom on line 1

Indexer changes

In metadata.py, when reading line 1, also extract forkedFrom if present and persist to the new columns. No new file reads -- already touching line 1.

Display changes

csb show <uuid>:

1. AMD_INTIGRITI__reply-caught-up__2026-05-01_DONE  ...
   parent: 23f58cd1-... (1.7 MB, last active 2026-02-23)
   forks:  dac78227-... (3.1 MB, forked 2026-04-30)
   ...

csb list:

  • Optional --show-chain flag that adds parent/forks lines to each entry.
  • Or always show a small indicator (e.g., [fork:23f58cd1]) next to forked sessions in the header.

csb chain <uuid> (new subcommand):

  • Walks parent pointers up to the chain root.
  • Walks (parent_session_id == <uuid>) to find descendants.
  • Displays the full ancestry tree.

Implementation approach

Phase 1 -- index + minimal display

  • Schema migration: add the three columns.
  • Indexer: parse forkedFrom from line 1 during normal indexing.
  • Display: csb show <uuid> includes parent: and forks: lines when applicable.

Phase 2 -- chain command

  • New csb chain <uuid> subcommand.
  • Walks the chain, prints a tree.
  • Optional --depth N to limit traversal.

Phase 3 -- chain-aware csb resume (depends on Alpha #14)

  • When resuming a session that has active forks, warn the user.
  • Optional: "resume the latest fork instead?" suggestion.

Acceptance criteria

  • Schema migration adds parent_session_id, parent_message_uuid, is_fork columns.
  • Indexer populates them from line 1's forkedFrom.
  • csb rebuild-index correctly populates the columns for existing sessions.
  • csb show <uuid> displays parent and forks when present.
  • csb chain <uuid> walks ancestry + descendants.
  • Test fixtures: a 3-deep chain (parent + child + grandchild); independent sessions; fork with multiple children.
  • CHANGELOG entry under ### Added.
  • README docs for new schema + commands.

Design considerations

  • Microcompact never forks. services/compact/microCompact.ts and services/compact/apiMicrocompact.ts contain no randomUUID or switchSession calls. Chain detection can ignore microcompact rows.
  • /clear produces a new sessionId but no forkedFrom. /clear is logically a fresh-start, not a fork. The line-1-forkedFrom discriminator handles this correctly: a /clear-cleared session's line 1 won't have forkedFrom, so is_fork=false.
  • Compaction in-place (the common case) writes a compact_boundary row at row N>1 without forkedFrom. No fork detection triggered. Correct.
  • Fork-of-rehomed-session (interaction with dz claude-rehome): if a session has been rehomed via hardlink to a different project-dir slug, its forks (created via /branch afterwards) will land in getProjectDir(getOriginalCwd()) -- the cwd-derived dir, not the rehomed dir. csb's chain walker should follow parent_session_id regardless of which project-dir slug the parent or child lives in. The walk is by UUID, not by location.

Related issues

Analysis

Detailed empirical baseline + source citations:

  • notes/architecture/2026-05-04__14-47-31__both_claude-code-session-fork-chain-and-compaction-model.md -- the data model, the 4-deep AMD_INTIGRITI chain, claude-code source citations
  • notes/architecture/2026-05-04__18-37-44__senior_q2-q3-q4-claude-code-session-internals.md -- session-ID minting inventory (only bootstrap/state.ts:331 and commands/branch/branch.ts:68 mint sessionIds in normal use), switchSession semantics, microcompact non-fork confirmation
  • notes/cli/2026-05-03__15-43-24__both_csb-start-at-semantics-and-folder-listing-config.md -- the originating UX investigation

Originating session reference: DAZZLECMD__fix-broken-claude-code-session (2026-05-03/04). The chain phenomenon was discovered while diagnosing why the AMD_INTIGRITI session was unresumable; the chain itself is what made the slug-vs-cwd divergence visible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestepicLarge multi-part feature or effort

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions