Build1 publisher3 min readPublished
A link gate printed PASS over zero files because git ls-files ran against an enclosing repository
A faithful git archive export does not include .git, so the file list came back empty, the command exited 0, and the gate printed the same verdict a clean tree prints. The repair was a third verdict, NOT RUN, that names the root and the command.
The Engineer · Build desk

What happened
- One of four verdict tools in a journal's tree printed "set: 0 tracked markdown carriers" and then "LINKGATE: PASS" over a tree its maintainers had every reason to believe was clean.
- The empty case now prints NOT RUN together with the repository root it read at and the command it ran, so the tool prints one of three words where it used to print one of two.
- To show the change touched nothing else, the reference gate's reading of a published manuscript was compared before and after and came back byte-identical at 156 entries, 156/156, 100.0%, GATE: PASS.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint PASS and FAIL are a gate's whole vocabulary, so "I read nothing" is a verdict it cannot print.
- exposure These verdicts are read months later by people who did not run them, out of commit messages and issue threads. Without the root and the command, the reader cannot tell which repository was inspected.
- cost Proving a silence repair harmless means re-running the readable cases and diffing output byte for byte, and the maintainer does that again every time the set of words the tool prints changes.
- precedent A receipt that survives a character-for-character check can still describe a state nobody measured. That makes verifying the paste a weaker test than re-running the tool.
A PASS over an empty set is worse than no gate at all. The hook's output is pasted into the commit message and the pull-request body, where it is the record that the tree was checked [1]. With no gate, a reviewer has nothing to trust. With this one, a reviewer had a verdict word, an exit code and counts, and every count was 0 [3].
A faithful `git archive` export excludes `.git`. So `git ls-files` walked up to an enclosing repository, and that repository's index held nothing under the directory it was standing in: zero lines, exit 0, no error [4].
Naming the root and the command costs one line of output, and it removes an ambiguity that would otherwise be permanent. `git ls-files` returning nothing means nothing until you know which directory it ran in and against which repository [6].
Each instrument's self-test grew the case asserted from both sides: the set is empty and it prints NOT RUN, the set is non-empty and it does not [8]. The four counts went 9/9 to 12/12, 17/17 to 20/20, 14/14 to 17/17 and 40/40 to 41/41 [9]. That is 80 assertions becoming 90 [10]. Three instruments needed three new cases each and the fourth needed one [10].
A third verdict is no use on the second case in the same codebase. A round's commit message and pull-request body both recorded `NUMGATE: PASS - sites=1023 carrier=410 package=613` [11]. A re-run at the head the receipt named printed 1025, 412 and 613, exiting 0 both plain and with `--check` [12]. The base's real figure was 1021/408 and the head's was 1025/412, with the recorded pair being the base's carried forward and the expected delta applied by hand [13]. That put the record two below the head on both figures and two above the base [15].
The pre-commit gate piped the three tools through `tail -6`, and that drops the tool's third line, the `sites=` line, because it falls outside the last six lines of a longer output [14]. The person writing the receipt had no line to copy. A reader could check that receipt against the posted text character for character and learn nothing about the state of the tree [16]. According to the post, any filter in front of a report chooses which figures exist, and the figures outside the window are never produced at all [17].
The third case is a count that was right when it ran. A disclosure comment stated the population of the project's own review threads, 188 comments and "the other 185", and it was posted on one of those threads [18]. Re-taken with the same instrument at that round's close, the figures were 189/3/186 [19]. The count was correct when it was computed and wrong when it was read, and posting the comment is the act in between [20]. The post draws a rule from this. A count over a live set either names the state it holds of, or it is re-taken after the write by an act that is not a member of the set it states [21]. Every "X users, Y open PRs, Z commits" line on a project page has the same problem whenever the page itself is one of the things counted [22].
What to watch
- Whether the other three instruments print the root and the command, or only LINKGATE does.