test(markdown-format): write the override-inert negative with a direct REPO_ROOT read - #2128
Conversation
…t REPO_ROOT read #2121 shipped a comment claiming the override's inertness was not behaviourally observable. That was wrong, and worse, it told a future maintainer to stop looking. A fourth instrument exists. The three instruments that fail all try to observe an EFFECT of REPO_ROOT: lint output cannot see it, because markdownlint-cli2 does its own config discovery and will not cross the repository boundary, so a forced-firing override still rewrites nothing; telemetry's data.file emits empty under a forced override, indistinguishable from a sink that never populated; exit status is 0 either way. The fourth reads REPO_ROOT DIRECTLY. The hook resolves a repo-local linter at "$REPO_ROOT/node_modules/.bin/markdownlint-cli2", so a distinguishable shim planted at both candidate roots names the root the hook actually computed. Two mechanics it depends on, both of which silently defeat it: the PATH copy of markdownlint-cli2 wins over the repo-local one, so directories carrying it are stripped while jq and git stay reachable; and the shim writes a MARKER FILE, because the hook captures stdout and stderr into a variable and swallows anything printed. The assertion is positive — the marker must read INNER. A wrongly-firing override yields OUTER or no marker, and both fail it. Measured against a hook whose probe was forced to `false`: correct reads INNER, forced reads no marker. The unusable-environment branch reports a visible ok rather than passing over in silence, which is what scripts/check-silent-skips.sh exists to catch. This suite has no skip helper and sources none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @kyle-sexton's task in 53s —— View job Security Review — PR #2128
Scope: single file, test-only — FindingsNo security issues found. The diff adds a negative-case test for the
No logic flaws, authorization gaps, or injection surfaces identified in this diff. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb0b3e094b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude finished @kyle-sexton's task in 5m 30s —— View job Claude's review
This PR replaces the "negative twin is unwritable" comment in I traced the logic against
I left two related 🟡 nits inline (lines 578, 586-591) on the PATH-stripping mechanism used to hide the PATH copy of I could not execute the test suite in this sandbox (bash execution beyond git required approval that wasn't available), so the above is static analysis against the hook's source, not a confirmed run. The author's own "Tests" section in the PR body ( No security-relevant surface in this diff (test-only shell script, no new trust boundary). |
|
Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
Three review findings on this PR, all correct and converging on the same fix.
The hand-rolled filter stripped every PATH directory containing a
markdownlint-cli2, which also removes whatever else lives beside it — git,
which hook::repo_root needs, or the coreutils resolve_repo_markdownlint calls.
The guard only re-checked that jq survived. On a layout where git shares a
directory with markdownlint-cli2, the hook's git probe would fail as
command-not-found, the override would fire, the marker would read OUTER, and
this case would fail while the production behaviour was correct. The comment
promised "jq and git must stay reachable" and nothing enforced the git half.
PATH is now left alone. A BASH_ENV command() override hides the binary from
`command -v` without touching PATH at all, which is the technique this suite
already uses at NO_MDLINT_ENV. A self-contained shim rather than that shared
one, because NO_MDLINT_ENV also stubs npx: a miss should surface as "no shim
ran" rather than be absorbed by an npx marker.
Discrimination re-verified against the new mechanism, since changing the
instrument invalidates the old control:
correct hook -> INNER forced-override hook -> no shim ran
Both cases pass:
ok: git present, dir is no repo: the override fires and CLAUDE_PROJECT_DIR terminates the walk
ok: git present: the override stays inert — the hook resolved REPO_ROOT to the git toplevel
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 5s —— View job Security Review — PR #2128 (HEAD
|
|
Claude finished @kyle-sexton's task in 2m 23s —— View job Code Review — PR #2128
SummaryThis PR is two commits: One issue survives the second commit: a stale comment paragraph (lines 576-580) was left over from the superseded PATH-stripping draft and now contradicts the paragraph immediately below it (lines 586-596), which correctly explains why I could not execute the test suite in this sandbox (no shell access beyond the read-only checks used for review). The logic was verified by reading the hook's source and tracing the control flow by hand, not by running the tests — the author's own "Tests" section in the PR body is author-claimed and unverified by me. No other correctness, error-handling, or maintainability issues found in this diff. Security review is out of scope for this lane (see |
…ase comment The paragraph survived from the first draft and contradicted the one directly below it: it said the directories carrying markdownlint-cli2 "must be stripped (jq and git must stay reachable)", while the next paragraph explains that PATH is left alone precisely because stripping removes git along with it. It now states the requirement — hide the binary from `command -v` — and points at the paragraph that says how, and why not via PATH. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 7s —— View job Security Review — PR #2128 (HEAD
|
|
Claude finished @kyle-sexton's task in 2m 33s —— View job Code Review — PR #2128 (HEAD
|
No linked issue
Summary
Test-only follow-up to #2121. That PR shipped a comment asserting the
REPO_ROOToverride'sinertness was not behaviourally observable, and left the negative case unwritten on that basis.
The assertion was wrong. A fourth instrument exists, it works, and the negative is now written.
The claim mattered beyond the missing case: a comment saying a thing cannot be observed tells the
next maintainer to stop looking.
Why three instruments failed
All three try to observe an effect of
REPO_ROOT:probe with
false— and the file is still not rewritten.markdownlint-cli2performs its ownconfig discovery and does not cross the repository boundary, so widening the hook's gate changes
no observable byte.
data.file, which is derived fromREPO_ROOTand looked like the obvious answer.The forced-override run emits an empty value rather than a relative-to-outer path — and empty
is also what a sink that never populated looks like, so the assertion could not separate a
regression from a flaky sink.
The fourth instrument reads
REPO_ROOTdirectlyThe hook resolves a repo-local linter at
"$REPO_ROOT/node_modules/.bin/markdownlint-cli2". Plant adistinguishable shim at both candidate roots and whichever one runs names the root the hook
actually computed. That is a read of the variable from outside the process, not an inference.
Control for the negative — a hook whose probe is forced to
false, so the override always fires:The assertion is positive: the marker must read
INNER. A wrongly-firing override producesOUTERor no marker at all, and both fail it.Two mechanics that silently defeat this
Recorded in the test file, because each one makes the instrument look like a dead end:
PATHcopy ofmarkdownlint-cli2wins over the repo-local one, so the shim never runswhile the real binary is reachable. The case strips only the directories carrying it, leaving
jqand
gitonPATH— remove those and the hook exits early for unrelated reasons.anything printed is swallowed. It must write a marker file.
Tests
The unusable-environment branch emits a visible
okrather than passing over in silence — thissuite has no skip helper and sources none, and a silent omission is exactly what
scripts/check-silent-skips.shexists to catch.Credit and provenance
The instrument was found by the session that wrote the guardrails work on #2100, after I concluded
the negative was unwritable. I reproduced it independently before building on it, including the
forced-override control above.
Worth recording alongside it: while testing this, that session hit the same defect the reviewer
found in #2121's first attempt — fixtures built under a Windows 8.3 shortname
(
C:/Users/KYLESE~1/…) whilehook::repo_rootreturns the long form, so the guard"$REPO_ROOT" == "$(dirname "$FILE")"compared two spellings of one directory and was always false.The branch never executed and the output looked plausible throughout. Same lesson as the rest of
this sequence: prove the fixture reached the path under test.
Related