Skip to content

Claude Code: a forked session copies prior records into a new file, so the same work lands on two cards #41

Description

@bmxcode

Surfaced by the corpus probe for #39, and separate from it. A per-day split does not close this.

The shape

Alongside the dominant append-on-resume model, the probe found a second, rarer path: a new transcript file that begins with a verbatim copy of a prior conversation's records — same uuids, same timestamps — re-stamped with a new sessionId, with new work chained onto the copied block by parentUuid.

Its anatomy, from the one instance observed:

  • the copied block is a leading prefix of the successor file, and is the predecessor file's records in their entirety
  • every copied record carries an identical timestamp in both files
  • no copied record keeps the predecessor's sessionId — the copy is re-stamped
  • the two files share no sessionId
  • the successor has no orphan parentUuid: it is self-contained and needs no pointer
  • nothing links the two files. There is no continuation field, in either direction. The only trace is the uuid overlap itself.

Why it matters to crate

Card.session_id is cards._last(live, "sessionId"), and Card.filename() is <date>-<session id>.md. Two files with different ids therefore produce two cards, and the copied block is rendered into both — the same prompts, prose, edits and commands, under two identities, on two cards that look like two separate pieces of work.

Downstream that is worse than a wasted file. /daily reads a day's cards as an account of that day and would see the work twice; /ingest owes each card a source page; and crate day orders by started:, so the duplicate sits adjacent to its original where it reads as a repetition rather than an artefact.

Note the cursor does not save us. .crate/state.json is keyed by Card.state_key — session id and day — so the two cards key differently and both are written. That is the correct behaviour for the key's actual job; the problem is upstream of it.

What is not known

One instance in the corpus. That establishes the shape exists; it says nothing about how often, or what causes it. The probe is observational and the files record no marker distinguishing a --resume from a --continue from a rewind from a crash recovery, so the trigger is unattributed.

This is explicitly not ready to be built against. #32 is the standing lesson about designing for a format model inferred from too little evidence — the correction there cost most of a deliverable.

Scope

  1. Characterise it before deciding anything. A controlled run is what the observational probe could not do: drive --resume, --continue, a rewind, and an interrupted session deliberately on a scratch project, then diff what lands on disk. That is a small experiment and it answers the question the corpus cannot.
  2. Only then decide whether crate should do anything. Options worth weighing when there is evidence to weigh them against, roughly in increasing cost:
    • Nothing. If the trigger turns out to be rare and self-inflicted, two cards with overlapping content may be an acceptable artefact of an immutable raw/.
    • Detect and skip. A successor whose leading records duplicate another file's uuids is a copy; card only the new records. Cheap per file, but detection is cross-file, and parse takes one session path — the same "forces parse to glob the sessions dir" objection that killed merge-on-capture in Codex: correlate a multi-file session (primary + resume segments) into one account #32.
    • Detect and link. Card both, but mark the successor as continuing the predecessor. Revisits the thread_id/parent_id frontmatter that has now been declined twice (#32, ADR-0015) — and would need a better reason than either previous attempt had.
  3. If the answer is "nothing", write that down. Codex: correlate a multi-file session (primary + resume segments) into one account #32 closed exactly that way and it was the right call.

Constraints

  • Deterministic/code (ADR-0004); fail-quiet capture on a Stop hook (ADR-0002) — it must never block session exit.
  • raw/ is immutable to Tier 1: a card already cited in a daily page's sources: cannot be deleted or renamed, so "clean up the duplicate afterwards" is not available.
  • Synthetic fixtures only; no real transcript enters this repo, and no figures from the probe.

Pointers

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions