mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #416] [MERGED] feat(cursor-review): mark the card stale when a push dismisses the approval, and opt-in start the next round #418
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#418
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/416
Author: @mattmillerai
Created: 10/12/2026
Status: ✅ Merged
Merged: 10/12/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-20262-stale-card-auto-rereview📝 Commits (6)
50de881feat(cursor-review): mark the card stale when a push dismisses the approval, and opt-in start the next round16a735efix(cursor-review): promise an automatic round only where auto-rereview accepts the tokenb7b8f70feat(cursor-approve): name the automatic round's UTC start time, not "in about N minutes"6ca27c2fix(cursor-review): keep the stale card honest when the approval is withdrawn on its own head or the relabel fails8597801fix(cursor-review): serialise auto-rereview waiters, cut off at the approval, skip reviewed heads5a30af9docs(cursor-approve): a review-thread run can cancel the auto-rereview wait without a replacement📊 Changes
12 files changed (+1314 additions, -49 deletions)
View changed files
📝
.github/cursor-approve/card.py(+184 -7)📝
.github/cursor-review/README.md(+3 -1)📝
.github/cursor-review/auto-approve.py(+339 -19)📝
.github/cursor-review/tests/test_auto_approve.py(+2 -2)📝
.github/cursor-review/tests/test_auto_retry_errored.py(+22 -8)➕
.github/cursor-review/tests/test_dismissal_rereview.py(+477 -0)📝
.github/cursor-review/tests/test_workflow_job_isolation.py(+4 -3)📝
.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(+221 -3)📝
docs/callers/cursor-approve.md(+50 -0)📝
docs/callers/cursor-review.md(+6 -3)📄 Description
ELI-5
The bot approves commit X, and its status card says "✅ Approved". Then someone pushes Y. GitHub throws the approval away, but the card keeps saying "Approved" and nothing starts a new review. This PR makes the card say what actually happened ("approved X, the head moved to Y, which needs its own round"). Callers can also opt in to have the next round start on its own after a short wait.
What changes
1. The dismiss path marks the card stale (
auto-approve.pymark_card_stale, called at the end ofdismiss-stale;card.pyrender_dismissed). This applies only to a PR in the approval lane (approve_max_severityset and the author passesapprove_authors), and only to apasscard whose commit the approver really approved, whether that approval is still live or was dismissed by the ruleset or by this job:no_decision/relabel: "Approved<old7>; the head moved to<new7>, which needs its own round."needs-human-reviewcapped/human, the same as the cap pathmax_rounds) without that labelcapped/human, saying no round starts by itself (nothing applied the label on a push)no_decision/push_then_relabel(a relabel alone would be deduped)skip-cursor-reviewno_decision/humanneeds-human-review,--all-approvals, or already dismissed)The rewrite keeps the round heading and the
cursor-approve:axes v1history. It adds acursor-approve-stale-headmarker, so later events on the same head write nothing and a newer push rewrites the card again. Apasscard with no matching approval is a round in progress (the pending or start card) and is left alone. A failed write only produces a warning. A run that could not withdraw every stale approval leaves the card alone and schedules nothing.2. Opt-in auto re-run. There are two new inputs:
auto_rereview_after_dismissal(defaultfalse) andauto_rereview_delay_minutes(default10, clamped to 30). The dismiss job sets anauto_rereviewoutput, and a newauto-rereviewjob waits for the delay, then runsauto-approve.py auto-rereview. That command re-reads everything live and relabels with the App token orAPPROVER_TOKEN, the same wayauto_retry_errored_roundsdoes, but only if all of these hold:skip-cursor-reviewnorneeds-human-reviewmax_roundsdupstep would no-op the relabel)The once-per-head record is written to the card before the relabel. It is a visible "started automatically" line plus a
cursor-approve:auto-rereview v1sentinel, whichupsertcarries through every later card write (bounded to 20 heads). So a head that flips back never loops. If the relabel itself fails, that line is rewritten to say so and to relabel by hand; the record stays. A veto orneeds-human-reviewstill moves a card whose automatic round already started to the human state. Underrun_without_labelthere is no auto re-run, because the push already starts the round.3. The card names when an automatic round starts.
card.retry_delay_phrase()now rendersat 05:42 UTC (5 min after the latest push)instead of "in about N minutes": the moment the wait is scheduled plus the clamped delay, in UTCHH:MM, with the date added only when the start falls on another UTC day. A newer push's dismissal rewrites the card, so the time shown is the live schedule. The existingauto_retry_errored_roundsnotice shares the helper (… (5 min after this round ended): remove and re-add …). With delay0the wording is unchanged.4. Docs: both input tables (
docs/callers/cursor-review.md,.github/cursor-review/README.md), a new "A push that dismisses the approval" section indocs/callers/cursor-approve.md, and the trust-model job count (now fourAPPROVER_TOKENjobs).Judgment calls
auto-rereviewjob has a concurrency group on PR + head (cancel-in-progress: false), so those waiters run one at a time and each one after the first reads the record and skips. Only one relabel happens per head. The cost is one runner per such event, sleeping during the delay.approve_authorsrule is mirrored in Python (author_opted_in), because the dismiss job deliberately does not depend ongate. A test pins its edge cases to the gate's shell semantics.round_reviewssinceneeds-human-reviewlast came off). If the count can't be read, the card is still rewritten as below the cap (round-cap enforces the cap at relabel time), but no auto re-run is scheduled.cmd_auto_retry's relabel was extracted intorelabel_review_labelso both jobs share it. Its behavior and existing tests are unchanged.Residual
auto_rereview_after_dismissal: trueto that caller.passcard two commits behind its head at round 1 of 5; the other is the same at round 5 of 5. They are in a private repo I cannot read from here. After the pin bump, the first event on each should rewrite its card: the first tono_decision/relabel, the second tocapped/human.auto-rereviewjob and the dismiss-step wiring are verified only by unit tests with a faked GitHub and by structural workflow tests. A realpull_request_targetrun with the App token has not happened yet.pull_request_review_threadrun in the samelabel-concurrency slot (e.g. resolving a thread during the wait) cancels it and does not schedule another; a laterpull_requestevent does. Documented in the job comment and caller docs; the card's "If none starts, re-run it by hand" covers it. A cancellation-proof waiter is proposed as a follow-up.Provenance
test_dismissal_rereview.pycovers the ticket's list: below cap →no_decision/relabelnaming both SHAs; at cap →capped/human; fires once after the delay on an unchanged head; a newer push, in flight, capped, draft, veto or human label → no relabel; flag off → card rewrite only. It also pinsAPPROVER_TOKENscheduling a round only onpull_request_target, an approval withdrawn on its own head, a failed dismissal leaving the card, a failed relabel correcting it, the approval-time in-flight cutoff (a relabel before the card was written, a label in the same second), a head that already had a round, and the job's concurrency group.0and unusable values.check_workflow_pins.pyOK,check_agents_md.pypasses (2 warnings that predate this PR), org-repo-literal lint OK.auto-rereviewwait is not made cancellation-proof againstpull_request_review_threadruns; the gap is documented and deferred.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.