mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #363] [MERGED] fix(cursor-approve): withdraw the bot's standing request for changes once auto-approve stops deciding #363
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#363
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/363
Author: @mattmillerai
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-19492-clear-standing-block📝 Commits (3)
eac5b2dfix(cursor-approve): withdraw the bot's standing request for changes once auto-approve stops deciding8c7c2f8fix(cursor-approve): address panel review on #363 (BE-19492)8eb10abfix(cursor-approve): rank the hand-off above a move on the deferred path too (BE-19492)📊 Changes
10 files changed (+531 additions, -83 deletions)
View changed files
📝
.github/cursor-approve/card.py(+8 -1)📝
.github/cursor-approve/tests/test_card.py(+3 -3)📝
.github/cursor-review/README.md(+4 -3)📝
.github/cursor-review/auto-approve.py(+196 -41)📝
.github/cursor-review/tests/test_approve_external.py(+101 -4)📝
.github/cursor-review/tests/test_auto_approve.py(+150 -8)📝
.github/workflows/cursor-approve.yml(+14 -7)📝
.github/workflows/cursor-review.yml(+10 -3)📝
docs/callers/cursor-approve.md(+27 -9)📝
docs/callers/cursor-review.md(+18 -4)📄 Description
Builds on #362's standing-block code (
card_for_none,post_standing_change_request,external_block), now merged tomain.Problem
#362 makes auto-approve leave a standing REQUEST_CHANGES (as the approver identity) whenever a round withholds approval. Only an approving round ever withdraws it. Some states mean no approving round will come, and in those states the bot's CHANGES_REQUESTED keeps blocking the merge until someone dismisses it by hand:
skip-cursor-review.dismiss-stalewithdraws approvals but only looks atAPPROVED.approve_max_severity, sodecideno longer runs.needs-human-review.card_for_none's capped branch left the earlier block standing, and approve-external'sneeds_humanoutcome posted a new one. The card's next step ("remove the label to reset the round count") clears nothing.Change
These rules apply only to the bot's own marked, unedited CHANGES_REQUESTED (
APPROVE_MARKERin the body, approver login). A human's review and a bot review someone edited are never touched. All selection goes through one new helper,own_change_requests, whichdismiss_own_change_requestsnow uses too.dismiss-stale(theDismiss stale auto-approvaljob runs on every event of an open PR). It now also withdraws the blocks when the live PR carriesskip-cursor-revieworneeds-human-review, or when the new--thresholdis passed empty. The workflow passesinputs.approve_max_severity. An absent--threshold(None) checks only the labels. Further details:needs-human-reviewnow withdraws every marked approval too, as the veto already did. Otherwise withdrawing the newer block would let an older on-head approval count again.withdraw_handed_off_change_requests. It lists the reviews, then re-reads the label on the live PR before dismissing. A run that finishes late therefore cannot sweep a block a newer round posted after the label came off. This costs one extra PR read, and only when there is a block to withdraw.decide, capped branch (card_for_none): posts no block, as before, and now also withdraws the earlier ones. The post-writeneeds-human-reviewrace path does the same:approve-external:needs_humanleavesEXTERNAL_BLOCK_OUTCOMES. That outcome andvetoednow withdraw the blocks throughEXTERNAL_HANDOFF_MESSAGESinstead of posting one.needs_humanis classified ahead ofsuperseded, both before and after the POST.not_approved/error/supersededkeep posting.withdraw --approve-gate capped(cursor-approve's start phase). On the round-cap path no cursor-reviewdecideruns, so this phase withdraws the blocks itself. It re-readsneeds-human-reviewoff the live PR rather than trusting the caller's gate. A failed dismissal turns the step red. The start-card step now runs under!cancelled()(guarded on a resolved login), so the card is still written.NEXT_HUMAN_CAPPED_TEXTnow names what actually clears the PR, without claiming the withdrawal succeeded: "the bot withdraws its own request for changes on this hand-off, so a human's review decides this PR — if one still shows, its withdrawal failed (see the run log) and it needs dismissing by hand. Removing theneeds-human-reviewlabel resets the round count."docs/callers/cursor-review.md(Auto-approve and Dismiss sections) anddocs/callers/cursor-approve.md("The status card contract"). The latter drops the "Known gap" paragraph #362 added. Also thecursor-reviewREADME and thecursor-approve.ymlheader.The ordering rule is unchanged: when a block is posted it is still "post new, then dismiss older by review id". A hand-off posts nothing, so it only dismisses.
Judgment calls
fail-round blocks too, not only no-decision ones. The ticket suggested restricting this to blocks auto-approve itself posted. I did not, for two reasons. No round runs on a vetoed PR, so afailblock is just as unclearable as a no-decision one. And the label is a human's explicit "the bot does not decide this PR". Restricting it would need a new body marker, and existing blocks would lack it.needs_humannow withdraws instead of posting. That reverses the block #362 added onneeds_human. The ticket offered both options. Withdrawing is the one where the capped next step describes an action that actually clears the PR. The card still reportscapped/human.test_a_push_keeps_the_block). The new dismissals fire only in states where no round can ever come. The old comment(A REQUEST_CHANGES withholds nothing, so it stays.)on decide's label-race check is rewritten to match.Residual
GITHUB_TOKEN. AGITHUB_TOKENlabel fires nolabeledevent, anddecidedoes not run on the cap run. The block then stays until the PR's next event, which runsdismiss-staleand sees the label. Under a GitHub App poster thelabeledevent fires and clears it right away. cursor-approve callers are covered by the start phase. The fix would mean giving theround-capjob the approver identity, which this PR does not do. Proposed as a follow-up from the panel review.skip-cursor-reviewveto applied by someone who cannot dismiss reviews (a triage role, or restricted dismissals) now clears the bot's block. That follows from the Judgment call above. Restricting it is a policy decision, proposed as a follow-up from the panel review.approve_max_severityper event. For example,${{ cond && 'low' || '' }}. On every event where it evaluates empty, the new path withdraws that caller's blocks. The docs already describe the input as a static kill switch. No such caller was checked: caller rosters are private secrets and not readable from here.ghunit tests only. No real PR was labelled or dismissed against.Tests
New or rewritten cases in
test_auto_approve.pyandtest_approve_external.py:dismiss-stale(the newHandedOffBlockTest):needs-human-revieweach withdraw only the bot's own block, along with every bot approval. A human's REQUEST_CHANGES, an edited bot block and an unmarked one stay.--threshold.decide: the capped round withdraws the earlier block (human and edited ones stay), and its card's next step says so. Both label-race paths withdraw by the posted id. A move together with the label posts no new block, on the posting path and the deferred (--defer-approval) path alike.approve-external:needs_humanposts nothing and withdraws only the bot's block, including when the label lands mid-POST and when the head also moved. A label gone by the re-read keeps the block.vetoedwithdraws too. A failed withdrawal is red.withdraw: oncappedit withdraws the block only while the live label is present (onpassor empty it does not). A failed withdrawal is red, and the start-card step keeps!cancelled(). The start phase passes the gate.needs_humanused to post a block, a REQUEST_CHANGES racing the label used to stay,needs-human-reviewused to leave an on-head approval, andwithdrawused to stay green on a failed withdrawal.Provenance
mainafter #362 merged; the PR's own commits applied unchanged.github/cursor-reviewunit suite: 1082 tests OK at the current head; the new moved-and-labelled deferred subtest fails against the previous ordering.github/cursor-approve(161),.github/workflow-pins(551) and.github/agents-md-integrity(46) suites OKcheck_workflow_pins.py,check_agents_md.pyandcheck-org-repo-literals.shpassghunit tests onlyneeds_humanwithdraws instead of posting.withdraw --approve-gatepath, which the ticket did not name, to cover the round-cap path.needs-human-reviewalso withdraws the bot's approvals, and the start-card step runs under!cancelled().test_card.py's raw U+200B lookaheads are escaped.🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.