fix(buglog): allocate bug ids from the high-water mark, not the array length - #69
Open
liveoakwag wants to merge 1 commit into
Open
fix(buglog): allocate bug ids from the high-water mark, not the array length#69liveoakwag wants to merge 1 commit into
liveoakwag wants to merge 1 commit into
Conversation
… length
`bug-${bugs.length + 1}` treats the array length as an id generator, and it is
not one. Any entry that arrives out of band - a hand-written bug, an id claimed
by another tool, a manual renumber, an entry removed from the middle - moves the
numbers away from the count. From then on `length + 1` re-issues an id that is
already in use.
The failure is silent and it compounds. Two entries share an id, `related_bugs`
pointers stop being decidable about which of the two they meant, and dedupe-by-id
starts merging unrelated bugs. Nothing raises an error at any point. In one real
log seven separate ids each ended up holding two different bugs before anyone
noticed, and untangling them afterwards meant guessing at intent.
Three call sites had the identical expression:
src/buglog/bug-tracker.ts
src/hooks/post-write.ts
src/templates/opencode-plugin/post-write.ts
The first two now share `nextBugId()` in src/utils/bug-id.ts. The opencode-plugin
template is copied into user projects and imports nothing outside its own folder,
so it gets a duplicate in its own fs.ts; a test pins the two implementations
together so they cannot drift.
Reading the ids rather than counting them is correct however the entries got
there. Ids that do not match the expected shape are skipped rather than thrown
on, so a hand-edited log still allocates instead of failing closed, and a
separate prefix (e.g. `bug-auto-`) allocates in its own namespace.
tests/bug-id.test.ts covers it, and each case also asserts what the old rule
returns, so the test demonstrates the defect rather than merely passing: on a
three-entry log whose ids run to 007 the old rule hands back bug-004, which is
already taken.
node --test tests/bug-id.test.ts 8/8
npm test 34/34
npx tsc --noEmit clean except the pre-existing
src/daemon/cron-engine.ts TS2503, which is
present on an unmodified checkout too
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 6, 2026
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.
bug-${bugs.length + 1}treats the array length as an id generator, and it is not one. Any entry that arrives out of band — a hand-written bug, an id claimed by another tool, a manual renumber, an entry removed from the middle — moves the numbers away from the count. From then onlength + 1re-issues an id that is already in use.The failure is silent and it compounds: two entries share an id,
related_bugspointers stop being decidable about which of the two they meant, and dedupe-by-id starts merging unrelated bugs. Nothing raises an error at any point. In one real log seven separate ids each ended up holding two different bugs before anyone noticed, and untangling them afterwards meant guessing at intent.The three call sites
The first two now share
nextBugId()insrc/utils/bug-id.ts. The opencode-plugin template is copied into user projects and imports nothing outside its own folder (nothing else in it does either), so it carries a duplicate in itsfs.ts— and a test pins the two implementations together so they cannot drift apart. Happy to restructure that if you would rather it were shared some other way.Reading the ids instead of counting them is correct however the entries got there. Ids that do not match the expected shape are skipped rather than thrown on, so a hand-edited log still allocates instead of failing closed, and a separate prefix (e.g.
bug-auto-) allocates in its own namespace.Verification
node --test tests/bug-id.test.ts— 8/8npm test— 34/34, 0 failnpx tsc --noEmit— clean apart from the pre-existingsrc/daemon/cron-engine.ts(52,27) TS2503 Cannot find namespace 'cron', which is present on an unmodified checkout toogit amonto pristinemain(f64e737) applies cleanly, suite still 34/34package-lock.jsondeliberately not committed — the project builds with pnpmThe test carries its control inline rather than by reverting the fix: every case also asserts what
bugs.length + 1returns, so it demonstrates the defect rather than merely passing. On a three-entry log whose ids run to007, the old rule returnsbug-004— already taken.Unrelated to #66, which is still open; this branch is cut from
mainand does not depend on it.🤖 Generated with Claude Code