Skip to content

A note on an unreachable commit is served as active, and its Supersedes retires the record that is live #351

Description

@MongLong0214

Found by a production audit of v0.5.1.

The sequence, which is ordinary

A  Limit: password hashing must stay bcrypt cost 12    Record-Id: r-old001
B  Limit: password hashing must stay argon2id          Record-Id: r-new001  Supersedes: r-old001
   B mirrored into refs/notes/commitlore    (what post-commit and squash inheritance do)
git reset --hard A                          (B is now unreachable)

git log says the repository contains exactly one record: r-old001, bcrypt.

context (index)    -> r-new001, active, src ["notes"]
context --no-index -> r-new001, active
stale              -> 0 superseded, 0 expired, of 1 record in 1 commit

Both failure directions at once. A record from an abandoned commit is served as active, and the record that survives in history is withheld because that abandoned commit retired it.

--no-index reproduces it, so this is the engine, not index staleness.

Cause

src/core/index-db.ts:658-679readNoteRecords enumerates git notes --ref=refs/notes/commitlore list with no reachability filter, reads them --no-walk (:683), and resolves paths the same way (:718). src/core/query.ts:585-600 folds them and :804 serves them without asking whether the annotated commit is in HEAD history.

stale disagrees because src/commands/stale.ts:131-134 does filter notes to commits in the walk. Same split as #350: two commands, one repository, different answers.

Window

Until git gc --prune removes the abandoned commit — default two weeks to ninety days. After gc --prune=now the answer correctly reverts, which confirms the mechanism.

No notes.rewriteRef is configured anywhere (grep -r rewriteRef src/ docs/ spec/ README.md → nothing), so a rebase never carries the mirror forward. The phantom is the expected outcome of "record a decision, then rebase before pushing" — which is a normal thing to do.

Fix

A reachability filter on readNoteRecords, matching what src/commands/stale.ts:131-134 already does. The two paths disagreeing is itself the signal that one of them is wrong.

Untested

test/notes.test.ts and test/stale.test.ts:691-812 cover notes/commit merge, divergence and notes-only supersession — never reachability.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions