You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 nosessionId
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
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.
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.
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.
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, sametimestamps — re-stamped with a newsessionId, with new work chained onto the copied block byparentUuid.Its anatomy, from the one instance observed:
sessionId— the copy is re-stampedsessionIdparentUuid: it is self-contained and needs no pointerWhy it matters to crate
Card.session_idiscards._last(live, "sessionId"), andCard.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.
/dailyreads a day's cards as an account of that day and would see the work twice;/ingestowes each card a source page; andcrate dayorders bystarted:, 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.jsonis keyed byCard.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
--resumefrom a--continuefrom 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
--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.raw/.parsetakes one session path — the same "forcesparseto glob the sessions dir" objection that killed merge-on-capture in Codex: correlate a multi-file session (primary + resume segments) into one account #32.thread_id/parent_idfrontmatter that has now been declined twice (#32, ADR-0015) — and would need a better reason than either previous attempt had.Constraints
raw/is immutable to Tier 1: a card already cited in a daily page'ssources:cannot be deleted or renamed, so "clean up the duplicate afterwards" is not available.Pointers
src/crate_wiki/claude.py—parse,_live_pathsrc/crate_wiki/cards.py—Card.session_id,Card.state_key,Card.filename(),writethread_id/parent_idhas been declined twice