Skip to content

The ref hygiene gate dies on a mid-rename tree instead of reporting (#940) - #948

Closed
philcunliffe wants to merge 1 commit into
masterfrom
fix/issue-940
Closed

The ref hygiene gate dies on a mid-rename tree instead of reporting (#940)#948
philcunliffe wants to merge 1 commit into
masterfrom
fix/issue-940

Conversation

@philcunliffe

Copy link
Copy Markdown
Contributor

Deferred finding 1 from the triage of PR #933, carried into #940.

What was wrong

trackedFiles() in test/core/llp-ref-hygiene.test.js fed git ls-files -z output straight into fs.readFileSync with no presence filter. ls-files answers from the index, not the working tree, so a path that is tracked but absent from disk (mid-rebase, mid-rename) threw ENOENT at module load. The whole gate then reported nothing at all instead of reporting that the annotations are fine. Loud, not silent, and developer-tree only, which is why it was deferred rather than blocking.

The fix

Filter the listing with fs.existsSync(path.join(REPO_ROOT, f)) before reading, with a comment saying why the index and the working tree can disagree.

The regression test

test/core/llp-ref-hygiene-absent-path.test.js, one test: the ref hygiene gate reports a result on a tree missing a tracked file.

It exercises the real gate rather than a copy of its logic. It builds a git shim on PATH that delegates to the real binary except that ls-files -z appends one extra path with no file behind it, then spawns node --test test/core/llp-ref-hygiene.test.js and asserts the run reports test results, never prints ENOENT, and exits 0.

One subtlety worth flagging for review: the child's environment has NODE_TEST_CONTEXT deleted. The node test runner refuses nested runs (node:test run() is being called recursively within a test file. skipping running files.), and without clearing it the child exits 0 having loaded nothing, so every assertion passes against a gate that never ran. The first assertion (# tests appears in the output) is the guard against that failure mode coming back.

Evidence

=== WITHOUT FIX ===
not ok 1 - the ref hygiene gate reports a result on a tree missing a tracked file
# pass 0
# fail 1
=== WITH FIX ===
ok 1 - the ref hygiene gate reports a result on a tree missing a tracked file
# pass 1
# fail 0

Without the fix the failure message carries the reported symptom verbatim: Error: ENOENT: no such file or directory, open '.../src/core/absent-from-the-working-tree.js' at .../llp-ref-hygiene.test.js:336.

Full suite: npm test 4529 pass, 0 fail, 1 skipped. npm run typecheck clean.

Fixes #940

…940)

`trackedFiles()` fed `git ls-files -z` straight into `readFileSync`. The
index is not the working tree, so a path tracked but absent from disk
(mid-rebase, mid-rename) threw ENOENT at module load and the whole gate
reported nothing at all instead of reporting that the annotations are
fine. Filter the listing with `existsSync` before reading it.

The regression test spawns the real gate with a `git` on PATH that
delegates to the real one except that `ls-files -z` names one extra path
with no file behind it, so the only difference from a normal run is the
condition under test. It clears `NODE_TEST_CONTEXT` for the child: the
test runner skips nested runs, and without that the child exits 0 having
loaded nothing and the assertions pass against a gate that never ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@philcunliffe

Copy link
Copy Markdown
Contributor Author

⚠️ Duplicate fix — this PR and #948 fix the same defect

Issues #940 and #942 are follow-ups from two different PRs (#929 and #933), but both
enumerated the same deferred finding: trackedFiles() in
test/core/llp-ref-hygiene.test.js feeds git ls-files -z output into
fs.readFileSync with no presence filter. Two workers fixed it independently.

Both branch off master and both modify test/core/llp-ref-hygiene.test.js, so
whichever merges second will conflict.

This needs a human call: pick one approach (or merge one and rebase the other onto
it). neutral is holding both and will not merge either. The regression tests differ in
kind and are not redundant, so the surviving PR may be worth taking the other's test
with it.

philcunliffe added a commit that referenced this pull request Aug 19, 2026
…porting (#942) (#947)

* The @ref hygiene gate dies on a mid-rename working tree instead of reporting (#942)

`git ls-files` reports the index, not the working tree, so a path it names can
have no file behind it: mid-rebase, mid-rename, or any other moment where the
two have not converged. `trackedFiles()` in the `@ref` hygiene gate fed that
list straight into `fs.readFileSync` at module scope, so one such path threw
ENOENT before a single test ran and took the whole file with it. A developer on
a transiently inconsistent checkout got a stack trace where a hygiene result
belonged.

Extract the listing into `test/helpers/tracked_files.js` and filter it to paths
that are present on disk, so every caller of the shared gate listing gets the
guarantee that what it returns can be read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Preserve ref-hygiene integration coverage from #948

---------

Co-authored-by: test <test@test.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Phillip Cunliffe <filco@Macmini.localdomain>
@philcunliffe

Copy link
Copy Markdown
Contributor Author

Closing as consolidated into #947. #948's subprocess integration test now runs in #947 alongside the shared tracked-file helper and unit coverage; it was adjusted only to accept both Node test-summary prefixes, then passed locally and on the Node 22/24 CI matrix before #947 merged.

@philcunliffe
philcunliffe deleted the fix/issue-940 branch August 25, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up: deferred review findings from PR #929

1 participant