Skip to content

Promote develop to main: Read an Empty prose_lint --diff as a Failed Base - #1567

Merged
ptr727 merged 1 commit into
mainfrom
develop
Sep 12, 2026
Merged

Promote develop to main: Read an Empty prose_lint --diff as a Failed Base#1567
ptr727 merged 1 commit into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Promotes one commit from develop to main.

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 -> main` promotion rather
than here.

## The defect

`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
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)"`. 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
""`. It was found while documenting that invocation for
`ptr727/aiopurpleair`.

## The fix

Both the scope computation and the refusal below it test `a.diff is not
None`, so an empty value reaches `changed_lines`, where `git diff ''`
fails with `fatal: bad revision ''`, and the existing refusal fires
naming the value it could not resolve.

The issue's own reproduction, against this branch:

```
$ python3 prose_lint.py --diff "" -- .
error: cannot diff against '', so the run cannot be scoped to changed lines. Refusing to scan
the whole tree instead, since that reports the existing backlog as though this change introduced
it. Check the ref exists and that the checkout carries its history.
exit=2
```

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:

- A unit test asserts the empty value reaches the resolver and takes the
refusal, rather than asserting the exit code alone, 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.

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.yml` rejects an unresolvable `$BASE`
before invoking the script, `.github/workflows/validate-task.yml` gates
that step on a pull request event, and the Husky, pre-commit and
`hub-fetch-run.py` callers all pass a literal `HEAD`. `scope_note`
already tested `base is None`, so no truthiness site was left
disagreeing with the new one, and `gate_provenance` keeps 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.py` and 1405 across the suite, `ruff
format --check`, `ruff check`, `mypy`, `prose_lint.py` over the gating
rule set, `repo_gate.py`, `spec/validate.py`, `build_dist.py --check`,
`canonical_review.py check`, and `docker_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 `!r` so 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](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 12, 2026 04:55
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b4d73170-bb48-47f1-ac40-1169ac312aec


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrowly scoped, matches the stated defect, and is covered by targeted tests that exercise both mocked and real-git behavior.

Pull request overview

Promotes the develop fix for .github/actions/prose-gate/prose_lint.py into main, ensuring that an explicitly provided-but-empty --diff value is treated as an invalid base (refusal) rather than being misread as “no diff” (whole-tree scan).

Changes:

  • Treat --diff "" as “diff requested” (presence) rather than “no diff” (truthiness), so the run refuses instead of silently widening.
  • Add unit + integration-style tests to pin the empty-diff refusal behavior (mocked resolver + real git).
File summaries
File Description
scripts/tests/test_prose_lint.py Adds regression tests covering empty --diff handling (mocked and real-git paths).
.github/actions/prose-gate/prose_lint.py Switches diff-scope routing from truthiness to presence checks for --diff.
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.

@ptr727
ptr727 merged commit c728624 into main Sep 12, 2026
9 checks passed
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.

prose_lint.py Treats an Empty --diff as No Diff and Widens a Scoped Run to the Whole Tree

2 participants