Repository navigation
A close-candidate reason can cite a commit hash nothing verifies — two flags cited different hashes for the same PR #210
Description
Two /ncc rulings on 2026-08-05 cited the same merged PR under two different commit hashes, and neither matches what the typed read reports. Both outcomes were correct, so this cost nothing today — which is precisely why it is worth fixing now rather than after it doesn't.
The evidence
Both flags rest on rainlanguage/raindex PR #2420 ("Remove Tauri desktop application"):
| source | hash cited |
|---|---|
raindex#960 flag (2026-07-29T13:19:24Z) |
merge commit 2c5695c2b3b7b4ae6eb2ddd093c7d2d8a953f154 |
raindex#1039 flag (2026-07-29T17:17:12Z) |
commit 2a31903402be |
pr_context on the PR |
headRefOid: c004c03c91b249d5be6d5f964e28f8d711b7b1f1 |
Three hashes, one PR. Head and merge commit differing is expected; the two producer-written hashes differing from each other is not — at most one can be right, and nothing in the pipeline noticed.
Why the existing guard does not catch it
citationEvidence (#193) reads the CITED CHANGE'S OWN DIFF and reports what the reason names that the diff cannot support. It worked as designed on #1039, correctly reporting "PR #2420 changes 188 files. This reason names no path or symbol its diff can be checked against."
But it is scoped to paths and symbols. A commit hash is neither, and nothing on the /ncc surface resolves a commit at all — the command says so in as many words ("There is no typed read on this surface that reaches a commit or a branch tip"). So a hash is the one part of a citation that is written by a model and checked by nobody.
The vetter did not catch it either: it upheld both, and its note on #1039 restated the producer's own framing.
Why it matters even though both rulings were right
A close-candidate is a proposal to destroy work, and the whole gate is built on the reason being a claim that gets checked. A hash that looks precise and is wrong makes a wrong conclusion look verified — the rain.dia#22 failure mode, except that one was caught because the citation named SYMBOLS the diff could be checked against. A hash has no such surface.
The severity is not in these two rulings. It is that the pipeline currently cannot distinguish "the producer read the merge commit" from "the producer wrote a plausible hash", and a human at /ncc cannot either.
Two shapes, and the first is probably right
-
Stop the producer writing hashes it cannot verify. The reason cites the PR REF (
owner/repo#n), and the tool resolves the commit at flag-write time — the same move asflag-blocked-on --blocked-bytaking typed refs rather than prose (ai:blocked-on sits with the vetter: typed dependency refs, automated clearance on dep merge, human polling removed #161/ai:blocked-on sits with the vetter: typed dependency refs, automated clearance on dep merge, human polling removed #164), and the same reasoning as Refuse a body/block path that is not this caller's own: absolute AND named for its subject #204's body-path guard: a value the tool can derive is not a value a caller should hand-type. This also makes the citation stable, since a rebase or a squash moves the hash but not the ref. -
Extend
citationEvidenceto resolve a cited hash against the named PR and report a mismatch, the way it currently reports named-but-absent symbols. Weaker than (1) — it catches the error after it is written rather than preventing it — but it covers reasons that cite a bare commit with no PR at all, which (1) does not.
They compose: (1) removes the common case, (2) catches what is left.
A second, smaller observation from the same two flags
raindex#960 and raindex#1039 are structurally identical — both moot because PR #2420 deleted the surface — yet reached the queue as wont-fix and already-fixed-on-main respectively. Both categories are in the legal vocabulary and the producer was candid in each ("Resolved by deletion, not by repairing the webkit dep"), so nothing was concealed and neither ruling turned on it. But the same fact pattern being labelled two ways means the category carries less information than it appears to, and openPr.blocksClose keys off exactly that distinction via flag.grounds — wont-fix reads as cites-no-landing and BLOCKS on an open PR, already-fixed-on-main citing a landing does not. So a miscategorised obsolescence claim can change whether an open PR vetoes the close. Worth deciding whether moot-by-removal has a home in the vocabulary, or whether one of the two categories is the intended one.
Check
A close-candidate reason cannot carry a commit hash that no typed read resolves. Either the hash is derived by the tool from a ref the tool verified, or the mismatch is reported as evidence at /ncc — and two flags citing the same PR can never disagree about its commit.
Related: #193 (citationEvidence, the guard this sits beside), #161/#164 (typed refs over prose), #204 (do not hand-type what the tool can derive).
Activity
thedavidmeister commentedon Aug 5, 2026
Refinement from a third ruling on the same PR (
raindex#1042, closed 2026-08-05).That flag cites PR #2420 as commit
2a31903402be— the same hash asraindex#1039. So the tally across three flags resting on one PR is:raindex#9602c5695c2b3b7b4ae6eb2ddd093c7d2d8a953f154raindex#10392a31903402beraindex#10422a31903402beTwo of three agree, so #960 is the outlier rather than the disagreement being undecidable, as the issue body currently frames it. That does not change the defect or either proposed fix — nothing in the pipeline resolves a cited hash, so the majority is a hint and not a verification, and a hash agreed on by two runs of the same model is exactly as unchecked as one written once.
If anything it sharpens the case for shape (1): the two agreeing flags were written in the same producer run, 34 seconds apart (17:17:12Z and 17:17:46Z), while the outlier came from an earlier run (13:19:24Z). Consistency within a run and drift across runs is what you would expect from a value the model re-derives each time rather than reads — which is the thing the ref-plus-tool-resolution fix removes.
Also worth recording from #1042:
citationEvidenceDID its job there, confirming the cited PR deletestauri-app/src/routes/+page.svelte +0/-40— the exact path the issue itself permalinked as the trigger. The guard works well where a reason names a path; the gap is specifically the hash.