Repository navigation
feat(cursor-review): carry an approval across base merges that only move a regenerated artifact - #464
Conversation
…ove a regenerated artifact carry_forward_unchanged_diff dismisses when the PR's own diff differs, which is right, but a PR rewriting a checked-in generated file the base also regenerates has other lines in it after every merge from the base. Each merge then costs a whole round on hand-written code that did not change. Add the opt-in carry_forward_regenerated_globs, empty by default. When the own diff differs it still carries only if the set of (name, previous, status) is the same, every differing file matches a glob, and every commit gained since the reviewed one is a merge with a parent on the live base. The carried approval, the log and the dismissal say so, and the approval names the files. Commits are read from the live base...head compare, not reviewed...head: the latter also lists every base commit a merge brought in, ordinary ones included, so requiring merges there would dismiss every real merge. A parent missing from the former is on the base, without a read per commit. A list the API cut short fails closed. A path outside a plain alphabet never matches a glob: its name lands in a body parsed back for its records, where a newline could forge a threshold line.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Eligibility checks .github/cursor-review/auto-approve.py, .github/cursor-review/tests/test_auto_approve.py |
Adds whole-path glob matching, path-character validation, and checks for unchanged file metadata and qualifying merge history. Tests cover accepted patterns and conditions that prevent carry-forward. |
Carry, reporting, and configuration .github/cursor-review/auto-approve.py, .github/workflows/cursor-review.yml, .github/cursor-review/README.md, .github/cursor-review/tests/test_auto_approve.py, docs/callers/cursor-review.md |
Adds the workflow input and CLI argument. Qualifying paths appear in carried-approval records and messages. Tests cover successful carries and workflow wiring. Documentation states that carried files are not re-reviewed and must be regenerated and verified by required CI. |
Sequence Diagram(s)
sequenceDiagram
participant Workflow as cursor-review.yml
participant Script as auto-approve.py
participant Compare as Live compare data
participant Approval as Carried approval
Workflow->>Script: Pass regenerated-file globs
Script->>Compare: Read changed files and commit history
Compare-->>Script: Return changes and commit data
Script->>Approval: Record qualifying regenerated paths
Approval-->>Workflow: Report carried approval and regenerated files
Merge Risk: 
Minimal · up to 904ed
-
The opt-in exception relies on the documented required checks for regenerated artifacts. No material merge blocker was found.
Pre-merge checks: 
2
Finishing Touches
-
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
Simplify code
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 6 finding(s).
| Severity | Count |
|---|---|
| 🟢 Low | 4 |
| ⚪ Nit | 2 |
Panel: 6/6 reviewers contributed findings.
The PR author picks the path and the regex backtracked through each `**`: one 4095-character path took 25s against two `**` and over 45s against three. Enough of those outlast dismiss-stale's 5 minutes, so the job dies and the stale approval stands. Walk the reachable path offsets once per glob character instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ELI5
A bot approval on a pull request gets withdrawn whenever the main branch is merged in, even if the only thing that moved is a file generated automatically from code. This lets a repository name such files, so that a merge which changes nothing else keeps the approval and doesn't cost a whole new review.
What
Adds the opt-in
carry_forward_regenerated_globsinput tocursor-review.yml, used withcarry_forward_unchanged_diff. Empty, the default, changes nothing.When the PR's own diff differs from what a round reviewed, the approval now still carries if all of these hold, on top of every existing precondition:
(filename, previous, status)is unchanged.The carried approval and the log name the files that differed. The dismissal message of the old approval says "apart from regenerated artifacts" instead of claiming the changes are identical.
Judgment calls
reviewed...headis a merge" would never pass. That list includes every base commit a merge brought in: on a real PR, one merge listed 118 commits, 1 of them a merge. I read the livebase...headcompare the carry already fetches instead. It lists only what the head has and the base doesn't, so the check costs no extra API call, and a parent missing from that list is on the base. The new commits are that list minus whatreviewedreaches inside it.extra_generated_globs. Globs match the whole path, so a baregen.jsonmeans the root file only, and[...]is literal. A wider match here means content nobody reviewed, so this errs towards dismissing. The docs state the difference.._/@+-is never treated as an artifact. Its name goes into a review body that is parsed back for its records, and a newline in a filename could forge a_Threshold:line.**is accepted. The input is set by the caller on its base branch, and the docs say a glob may only name files a required check regenerates and verifies. A broad directory glob couldn't be told apart anyway.Testing
CarryForwardRegeneratedTestcovers every test case in the acceptance criteria, plus chained carries, the fail-closed cases above, glob semantics and forged paths. To share the fixture,CarryForwardTestis split into a fixture class and its tests, with those tests unchanged.head,name,status, …) and I did not judge them one by one; the 10 are every doc line naming the carry inputs or their messages.Provenance
cursor-review(1769),cursor-approve(342) andworkflow-pins(551, 43 skipped) suites.check_workflow_pins.py,check_agents_md.py(passed with 2 warnings) and the org-repo literal lint all passown_changesandregenerated_only. All three reviewed heads carry, naming only the artifact. An earlier head with ordinary commits after it dismisses.actionlint(not installed here); the workflow change was checked by a YAML parse and a wiring test only. No live dismiss-stale run.base...headcompare, notreviewed...head(see Judgment calls).Residual
The new input is not switched on anywhere yet, and these acceptance items can't be done from this repo:
carry_forward_regenerated_globsto the generated file's path.Not changed, by design: a generated file whose patch is too large for the compare API to return still dismisses. The one measured file has a patch today (546 changed lines), so a much larger rewrite of it would fall back to dismissal.