Skip to content

feat(cursor-review): carry an approval across base merges that only move a regenerated artifact - #464

Merged
mattmillerai merged 2 commits into
mainfrom
be-24824-carry-forward-regenerated-globs
Nov 1, 2026
Merged

mattmillerai merged 2 commits into
mainfrom
be-24824-carry-forward-regenerated-globs

Conversation

@mattmillerai

@mattmillerai mattmillerai commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

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_globs input to cursor-review.yml, used with carry_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:

  1. The globs are non-empty.
  2. Every commit the PR gained since the reviewed commit is a merge with a parent on the live base.
  3. The set of (filename, previous, status) is unchanged.
  4. Every file whose normalized patch differs matches a glob, and every other patch is identical.

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

  • Which commits get checked. Reading the rule as "every commit in compare reviewed...head is 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 live base...head compare 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 what reviewed reaches inside it.
  • Extra fail-closed cases. These all dismiss: a retarget with no new commit, a head that no longer contains the reviewed commit (a rebase), and a commit list cut short by the API's 250 cap.
  • Glob syntax is stricter than extra_generated_globs. Globs match the whole path, so a bare gen.json means 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.
  • Globs are not matched with a regex. The PR author picks the path, and backtracking on a long one could outlast the job and leave the stale approval standing, so the matcher walks reachable path offsets instead.
  • Odd paths never match. A path with anything outside letters, digits and ._/@+- 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.
  • No guard on broad globs. ** 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

  • New CarryForwardRegeneratedTest covers every test case in the acceptance criteria, plus chained carries, the fail-closed cases above, glob semantics and forged paths. To share the fixture, CarryForwardTest is split into a fixture class and its tests, with those tests unchanged.
  • Doc-drift sweep: 10 references checked, 4 updated. The sweep's other matches were on common words (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

  • Authored by: agent-work loop
  • Verified:
    • Tests: all green on this head — cursor-review (1769), cursor-approve (342) and workflow-pins (551, 43 skipped) suites.
    • Lints: check_workflow_pins.py, check_agents_md.py (passed with 2 warnings) and the org-repo literal lint all pass
    • Mutation-tested on the previous commit: 11 mutations of the new logic, each caught by a test. Two survived at first, and the tests were tightened to catch them.
    • Glob matcher: agreed with the regex it replaced on 400,000 random glob/path pairs, and its stall test fails against that regex.
    • Replay on real data, read-only, on the previous commit: real compare responses from a consumer PR that lost its approval this way, run through own_changes and regenerated_only. All three reviewed heads carry, naming only the artifact. An earlier head with ordinary commits after it dismisses.
    • Not run: actionlint (not installed here); the workflow change was checked by a YAML parse and a wiring test only. No live dismiss-stale run.
  • Deviations:
    • Merge-only commits are read from the live base...head compare, not reviewed...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:

  • Enable it on the consumer. Bump that repo's pin to this PR's merge commit and set carry_forward_regenerated_globs to the generated file's path.
  • Check the required-check precondition first. I did not confirm that the test regenerating that file runs in a required check on the consumer. The glob must not be set until it does, because without it nobody vouches for the file's content.
  • Verify on a real PR. After enabling, confirm on a pricing PR that a main merge moving only that file keeps the approval, with the carry visible in the dismiss-stale log. Only the decision logic has been replayed against real data; no approval has been posted this way.

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.

…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.
@mattmillerai mattmillerai added cursor-review Multi-model cursor review agent-coded Authored by the agent-work loop labels Nov 1, 2026
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Team
  • Run ID: fec2bff7-7fee-41ed-996f-1f096d20f8cb
Commits

Reviewing files that changed from the base of the PR and between 7c55d05 and 904ed45.

Files selected for processing: 2
  • .github/cursor-review/auto-approve.py
  • .github/cursor-review/tests/test_auto_approve.py

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Walkthrough

The change adds carry_forward_regenerated_globs to unchanged-diff approval carry-forward. It permits configured file differences only when file metadata matches and added commits are qualifying base merges. Carried approvals and logs identify those files.

Changes: Generated-file carry-forward

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
Loading
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: Passed 2
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 6 finding(s).

Severity Count
🟢 Low 4
⚪ Nit 2

Panel: 6/6 reviewers contributed findings.

Comment thread .github/cursor-review/auto-approve.py Resolved
Comment thread .github/cursor-review/auto-approve.py Resolved
Comment thread .github/cursor-review/auto-approve.py Resolved
Comment thread .github/workflows/cursor-review.yml Resolved
Comment thread .github/cursor-review/auto-approve.py Resolved
Comment thread .github/cursor-review/auto-approve.py Resolved
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>
@mattmillerai
mattmillerai merged commit 39a61f1 into main Nov 1, 2026
8 checks passed
@mattmillerai
mattmillerai deleted the be-24824-carry-forward-regenerated-globs branch November 1, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded Authored by the agent-work loop cursor-review Multi-model cursor review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants