mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #365] [MERGED] feat(cursor-review): re-run a round the head outran, once per PR (BE-19526) #367
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#367
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/365
Author: @mattmillerai
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-cursor-approve-auto-retry📝 Commits (4)
9828f5cfeat(cursor-review): re-run a round the head outran, once per PR (BE-19526)7f4dedeMerge remote-tracking branch 'origin/main' into matt/be-cursor-approve-auto-retryc432a8efix(cursor-review): relabel from the run's last job, and only once the marked block landed (BE-19526)4355407test(workflow-pins): count the auto-retry job's guarded checkout (BE-19526)📊 Changes
9 files changed (+652 additions, -13 deletions)
View changed files
📝
.github/cursor-approve/card.py(+14 -0)📝
.github/cursor-review/auto-approve.py(+202 -7)📝
.github/cursor-review/tests/test_auto_approve.py(+252 -0)📝
.github/cursor-review/tests/test_workflow_job_isolation.py(+4 -1)📝
.github/cursor-review/tests/test_workflow_panel_integrity.py(+11 -0)📝
.github/cursor-review/tests/test_workflow_ref_guard_parity.py(+1 -1)📝
.github/workflow-pins/tests/test_check_workflow_pins.py(+5 -2)📝
.github/workflows/cursor-review.yml(+131 -1)📝
docs/callers/cursor-approve.md(+32 -1)📄 Description
ELI5
A review panel takes ~45 minutes. If you push during that window, the round ends up judging a commit the PR has already moved past, so it refuses to decide — "the PR head moved while the review ran". That burned a whole panel, and the only way out was for a human to spot the status card and remove/re-add the
cursor-reviewlabel by hand. Until someone did, the PR sat behind a standing request for changes with nothing coming to clear it.Now the round re-runs itself. Once per PR.
Why not "review every push"
That was the obvious fix and it's the wrong one — it makes every push to a labelled PR cost a panel, across every consumer repo.
This doesn't react to your push. It reacts to a round that already finished and was wasted. Panels stay bounded by invalidations, not by pushes — a healthy PR runs no extra round however often you push to it. The long panel latency actually helps: by the time the round ends and notices, the pushing has usually stopped, so the retry reviews the settled head instead of chasing a moving one.
When it fires
Only where a fresh round can actually decide differently: the head moved. That's what makes the next round read different content, so the gate's same-SHA
dupcheck can't skip it.The ceiling
One per PR, counted off the PR's own reviews rather than a run-local counter — this job is stateless and the retry's round is a different run. Each retry marks its standing block with
<!-- cursor-review-auto-retry -->, and a marked block still counts after a later round dismisses it, so the bound holds across rounds and runs.One is deliberate. A second only helps while the author is still pushing, and re-running on every settle is the cost this exists to avoid.
Fails back, never into a loop
max_rounds→ no retry (the re-run would cap out and skip its panel)--can-relabel trueretries; theGITHUB_TOKENfallback passesfalse, because aGITHUB_TOKEN-applied label fires no workflow run (the reasoncursor-review-auto-label.ymlexists)auto-retryjob goes red with an::error::naming the label; the card already says "if no new round starts, re-run it by hand"Where the relabel runs
Not in decide. decide sets
auto_retry=true, and the relabel runs in a new last job,auto-retry("Re-run the round (head moved)"). That jobneedspost-review, panel-integrity, blocking-gate and notify-complete under!cancelled(). Theunlabeled/labeledevents it fires land in the caller'scancel-in-progressgroup, so if decide relabelled itself it would cancel the run's own Blocking gate and Panel integrity checks. The DELETE URL-quotes the label and tolerates a 404, and the re-add is tried twice.The contract marker stays
relabel, so an agent reading the card waits for the new round either way — only the prose changes, so nobody re-does by hand what already fired.Caller impact
None required. Whichever token a caller already configures (
APPROVER_TOKEN, or thebot_app_idApp) needs label write on the repo; a caller on neither simply never auto-retries.Provenance
Found while diagnosing why two live PRs were sitting on standing blocks nobody had noticed — in both cases a branch update during the panel. Documented in
docs/callers/cursor-approve.md.Verified:
--can-relabel, label quoting, the 404-tolerant DELETE and the retried re-addcheck_workflow_pins.py,check_agents_md.py --root .andcheck-org-repo-literals.shpassDeviations:
auto-retryjob. Four structural workflow tests were updated for it: the cursor-review guard count went 10→11 and the workflow-pins guarded-site count went 21→22, the bot-key allowlist gainedauto-retry, and panel-integrity's "gates nothing" test now allows this one ordering-only dependent and pins that it runs under!cancelled()and never reads panel-integrity's resultTests
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py'— 1144 passpython3 -m unittest discover -s .github/cursor-approve/tests -p 'test_*.py'— 161 passpython3 -m unittest discover -s .github/workflow-pins/tests -p 'test_*.py'— passpython3 .github/workflow-pins/check_workflow_pins.py— OKpython3 .github/agents-md-integrity/check_agents_md.py --root .— passedbash .github/lint/check-org-repo-literals.sh— OK🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.