Read an Empty prose_lint --diff as a Failed Base, Not as No Diff - #1566
Conversation
`prose_lint.py` decided whether a run was diff-scoped by the truthiness of `--diff` rather than by its presence, so an empty value read as no `--diff` at all. The run then widened to the whole tree and reported the existing backlog as though the change had introduced it, which is the exact outcome the refusal on the next line exists to prevent. An unresolvable ref took that refusal while an empty string walked past it. The exposed path is a local run whose caller substitutes a command's output, `--diff "$(git merge-base origin/develop HEAD)"`. Where `origin/develop` does not exist, as in a single-branch clone of `main`, `git merge-base` writes only to stderr and the script receives `--diff ""`. The composite action is unaffected, since it verifies `$BASE` resolves before calling the script. Testing for presence routes the empty value into `changed_lines`, where `git diff ''` fails with `fatal: bad revision ''`, so the existing refusal fires and names the value it could not resolve. The reproduction in the issue now exits 2 with `cannot diff against ''` where it previously exited 1 on a line the change never touched. ## What changed - `main` tests `a.diff is not None` in both the scope computation and the refusal below it, with a comment naming the caller shape that produces an empty value, so a later edit does not read the presence test as a stylistic choice. - A regression test asserts the empty value reaches the resolver and takes the refusal, rather than asserting only the exit code, which the whole-tree path could also produce for its own reasons. - A second test runs the same case against a real repository, since the first mocks the resolver and would stay green if git ever resolved an empty revision. Resolving one to an empty scope reads as a clean tree, which is the other half of what the routing closes. Fixes #1537 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, preserves existing refusal semantics, and adds targeted tests that cover both mocked and real-git behavior for the empty --diff case.
Pull request overview
This pull request fixes a scoping defect in .github/actions/prose-gate/prose_lint.py where an empty-but-present --diff value (e.g., --diff "") was treated like the option was absent, causing an unintended whole-tree scan and misattributed backlog findings. The change makes diff-scoping depend on --diff presence (is not None) rather than truthiness, and adds tests to pin the behavior for both mocked and real-git execution paths.
Changes:
- Treat
--diff ""as an invalid diff base (presence-based) so it reacheschanged_linesand triggers the existing refusal when git cannot diff against it. - Add a unit test asserting the empty diff value is passed through to the resolver and results in the refusal exit code.
- Add an integration-style test asserting a real
git diff ''failure is what enforces the refusal (protecting against future behavior drift).
File summaries
| File | Description |
|---|---|
| .github/actions/prose-gate/prose_lint.py | Switch --diff handling from truthiness to presence to prevent silent widening on empty values and ensure the refusal path triggers. |
| scripts/tests/test_prose_lint.py | Add unit + integration coverage for --diff "" to ensure it is refused and routed consistently. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…Base (#1567) Promotes one commit from `develop` to `main`. - `e4aa5d8` Read an Empty prose_lint --diff as a Failed Base, Not as No Diff (#1566) `prose_lint.py` decided whether a run was diff-scoped by the truthiness of `--diff` rather than by its presence, so `--diff ""` read as no `--diff` at all, the run widened to the whole tree, and it reported the existing backlog as though the change had introduced it. That is what the refusal on the next line exists to prevent, and an unresolvable ref took that refusal while an empty string walked past it. The exposed path is a local run whose caller substitutes a command's output, `--diff "$(git merge-base origin/develop HEAD)"`, which yields an empty string wherever the substituted command fails. Both the scope computation and the refusal now test `a.diff is not None`, so an empty value reaches the resolver, `git diff ''` fails, and the refusal fires naming the value it could not resolve. Two tests cover it, one mocking the resolver and one against a real repository, since the mocked one alone would stay green if git ever resolved an empty revision. No caller in the tree can pass an empty-but-present value today, so nothing that works now starts refusing. Copilot reviewed the feature pull request at its merged head, full coverage, no findings and none suppressed. Closes #1537
Fixes #1537, which registers on the
develop -> mainpromotion rather than here.The defect
prose_lint.pydecided whether a run was diff-scoped by the truthiness of--diffrather than by its presence, so an empty value read as no--diffat all. The run then widened to the whole tree and reported the existing backlog as though the change had introduced it, which is the outcome the refusal on the very next line exists to prevent. An unresolvable ref took that refusal while an empty string walked past it.The exposed path is a local run whose caller substitutes a command's output,
--diff "$(git merge-base origin/develop HEAD)". Whereorigin/developdoes not exist, as in a single-branch clone ofmain,git merge-basewrites only to stderr and the script receives--diff "". It was found while documenting that invocation forptr727/aiopurpleair.The fix
Both the scope computation and the refusal below it test
a.diff is not None, so an empty value reacheschanged_lines, wheregit diff ''fails withfatal: bad revision '', and the existing refusal fires naming the value it could not resolve.The issue's own reproduction, against this branch:
It previously exited 1 on
a.md:1: semicolon, a line the change never touched.Tests
Two, because one of them alone would not hold the fix:
Each was proved by reverting the two production lines, where both fail with
2 != 1, the whole-tree widening itself.Reach
No caller in the tree can pass an empty-but-present value today, so nothing that works now starts refusing.
.github/actions/prose-gate/action.ymlrejects an unresolvable$BASEbefore invoking the script,.github/workflows/validate-task.ymlgates that step on a pull request event, and the Husky, pre-commit andhub-fetch-run.pycallers all pass a literalHEAD.scope_notealready testedbase is None, so no truthiness site was left disagreeing with the new one, andgate_provenancekeeps truthiness deliberately, where an empty value falling through to the next source is what it wants.Verification
Full local gate set green on this head: 270 cases in
scripts/tests/test_prose_lint.pyand 1405 across the suite,ruff format --check,ruff check,mypy,prose_lint.pyover the gating rule set,repo_gate.py,spec/validate.py,build_dist.py --check,canonical_review.py check, anddocker_lint.py's seven linters. No canonical unit changed, so no carried-content pass was owed.Three local strict-review passes ran at the strongest available tier. The first raised three findings and the second six, all answered, which is the two-round edit budget spent. The third found no behavioral defect and four prose findings, open and listed here rather than fixed, per that budget: a missing comma after a fronted participle at
test_prose_lint.py:1906, a relative pronoun binding to the nearer noun at:2697, an unexplained positive control at:2705, and both new tests asserting the exit code where the sibling refusal test also asserts the message text.One earlier finding was declined with evidence: that the refusal's closing sentence, "Check the ref exists and that the checkout carries its history", names a remedy that does not fit an empty value. It is pre-existing text this change did not write, the message prints the value with
!rso an empty base displays as'', and growing a per-input remedy enumeration has repeatedly cost this repository more than it returned.🤖 Generated with Claude Code