Found during post-merge verification of #352. Pre-existing — reproduced identically on c9a83a2 (before the #352 merge) and on ca538a6 (after), so this is not a regression from that change.
Reproduced
A two-block message declaring one Record-Id in both blocks, validated in a full clone with the notes refspec configured:
$ commitlore validate -f dup.txt
shape failed · references failed
4: duplicate-id Record-Id — got "r-dup352z", want "exactly one record per Record-Id"
8: duplicate-id Record-Id — got "r-dup352z", want "exactly one record per Record-Id"
4: duplicate-id Record-Id — got "r-dup352z", want "exactly one record per Record-Id"
8: duplicate-id Record-Id — got "r-dup352z", want "exactly one record per Record-Id"
commitlore: 4 violations (SPEC §6) — the message was not modified
$ echo $?
1
Two duplicates. Four reported. --json carries all four objects, byte-identical in pairs:
{"line":4,...,"rule":"duplicate-id",...},{"line":8,...},{"line":4,...},{"line":8,...}
Cause
Both checks find it independently. The shape check catches a Record-Id repeated inside one message; checkReferences catches the same id colliding across the message's own blocks. Neither knows the other ran, and the violations are concatenated without dedup.
The reference-check half only becomes visible once the unfetched gate is past — in a repository with no notes refspec, references short-circuits to not-checked and only the shape half prints. That is why this survived: the common local case shows two, and the case that shows four needs a configured mirror.
Why it matters
validate output is what a person reads at the moment their commit was just rejected, and the repair loop reads --json. A doubled list makes a two-problem message look like a four-problem one, and the count in the summary line is the number a reader uses to decide how much work they are in for. It also means an automated repair loop is handed two identical instructions for one edit.
Shape of the fix
Dedup violations on (line, rule, key, value) before formatting and counting — or decide which check owns duplicate-id and let the other stay silent on it. The second is cleaner but is a contract decision about what each check class means; the first is safe and local. Whichever is chosen, the summary count must match the number of distinct problems.
Found during post-merge verification of #352. Pre-existing — reproduced identically on
c9a83a2(before the #352 merge) and onca538a6(after), so this is not a regression from that change.Reproduced
A two-block message declaring one
Record-Idin both blocks, validated in a full clone with the notes refspec configured:Two duplicates. Four reported.
--jsoncarries all four objects, byte-identical in pairs:{"line":4,...,"rule":"duplicate-id",...},{"line":8,...},{"line":4,...},{"line":8,...}Cause
Both checks find it independently. The shape check catches a
Record-Idrepeated inside one message;checkReferencescatches the same id colliding across the message's own blocks. Neither knows the other ran, and the violations are concatenated without dedup.The reference-check half only becomes visible once the
unfetchedgate is past — in a repository with no notes refspec,referencesshort-circuits tonot-checkedand only the shape half prints. That is why this survived: the common local case shows two, and the case that shows four needs a configured mirror.Why it matters
validateoutput is what a person reads at the moment their commit was just rejected, and the repair loop reads--json. A doubled list makes a two-problem message look like a four-problem one, and the count in the summary line is the number a reader uses to decide how much work they are in for. It also means an automated repair loop is handed two identical instructions for one edit.Shape of the fix
Dedup violations on
(line, rule, key, value)before formatting and counting — or decide which check ownsduplicate-idand let the other stay silent on it. The second is cleaner but is a contract decision about what each check class means; the first is safe and local. Whichever is chosen, the summary count must match the number of distinct problems.