Skip to content

fix: give a track an id the next ten thousand tracks cannot take - #51

Merged
ralyodio merged 1 commit into
masterfrom
worktree-track-id-collision
Aug 12, 2026
Merged

fix: give a track an id the next ten thousand tracks cannot take#51
ralyodio merged 1 commit into
masterfrom
worktree-track-id-collision

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Fixes SQLITE_CONSTRAINT: UNIQUE constraint failed: tracks.id when building a reel from clips.

Cause

newId built every id from the first eight hex characters of a UUID — 32 bits, 4.3 billion values. Ample for projects, videos and reels; far too few for tracks, the one table that fills up on its own. Detection writes a row per track, and a single production video already carried 8,394 of them (the number is in analyze.ts, in the comment explaining why a score-only re-run reports zero). Vetting a shirt before a track may claim whoever is standing there (#50) trades spliced identities for fragments, so it multiplies that count rather than reducing it.

A project assembled from a dozen clips holds the sum, because detection clears the tracks of the video it is re-analysing, not of the project. That is why building from clips is where this surfaced.

The birthday bound, measured

tracks P(a run dies)
8,394 0.8%
25,000 7%
50,000 25%
100,000 69%

Not a rare collision — the expected outcome of the flow that accumulates the most.

Fix

Ids carry 64 bits (randomBytes(8)), so the same 100k rows collide with probability 3e-10.

Widening rather than retrying on the constraint is deliberate: a retry loop would hide the rate instead of removing it, and it would have to sit on every insert to be honest about where else this was possible. Ids already written stay valid and stay readable — eight characters and sixteen cannot collide with each other — so no migration is owed and no existing project has to be re-analysed for the fix to hold. Re-analysing a broken project now succeeds because it re-inserts with wide ids.

Checked for format assumptions: parseFrameFileName splits on the last _ (length-agnostic), and the upload CLIENT_ID regex already accepted {8,32}. One test pinned the old width and is updated.

Verification

packages/core/src/ids.test.ts generates 200k ids and expects 200k distinct. Against the old scheme it returns 199,995 — confirmed by temporarily reverting the implementation and watching the test fail.

Full suite 691 passed / 8 skipped; pnpm lint and pnpm -r typecheck clean.

🤖 Generated with Claude Code

"SQLITE_CONSTRAINT: UNIQUE constraint failed: tracks.id", partway through
building a reel from clips.

Every id in the system was the first eight hex characters of a UUID: 4.3
billion values, which is ample for the tables a person can name and far too
few for the one that fills up on its own. Detection writes a row per track,
and a single production video already carried 8,394 of them -- the number is
in analyze.ts, in the comment explaining why a score-only re-run reports zero.
Vetting a shirt before a track may claim whoever is standing there (#50) trades
spliced identities for fragments, so it multiplies that count rather than
reducing it, and a project assembled from a dozen clips holds the sum: detection
clears the tracks of the video it is re-analysing, not of the project.

The birthday bound over 32 bits, measured rather than assumed: 8k tracks lose
one run in 125, 50k lose one in four, 100k lose more often than they win. That
is not a rare collision, it is the expected outcome of the flow that
accumulates the most, which is why building from clips is where it surfaced.

So ids carry 64 bits. The same 100k rows now collide with probability 3e-10.
Widening rather than retrying on the constraint is the point: a retry loop
would hide the rate instead of removing it, and it would have to sit on every
insert to be honest about where else this was possible. Ids already written
stay valid and stay readable -- eight characters and sixteen cannot collide
with each other, so no migration is owed and no existing project has to be
re-analysed for the fix to hold.

The test generates 200k ids and expects 200k distinct ones. Against the old
scheme it returns 199,995.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit e1f105f into master Aug 12, 2026
4 checks passed
@ralyodio
ralyodio deleted the worktree-track-id-collision branch August 12, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant