mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #361] [MERGED] fix(cursor-approve): honour delta non-gating marks in approve-external thread resolution #361
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#361
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/361
Author: @mattmillerai
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @mattmillerai
Base:
main← Head:be-19482-approve-external-delta-scope📝 Commits (3)
5fb4d29fix(cursor-approve): honour delta non-gating marks in approve-external thread resolution455498dMerge origin/main into be-19482-approve-external-delta-scope03e6456fix(cursor-review): report approve_scope_effectivefullwhenever no decision stands📊 Changes
8 files changed (+196 additions, -14 deletions)
View changed files
📝
.github/cursor-review/README.md(+1 -1)📝
.github/cursor-review/auto-approve.py(+41 -7)📝
.github/cursor-review/tests/test_approve_external.py(+16 -3)📝
.github/cursor-review/tests/test_auto_approve.py(+99 -2)📝
.github/workflows/cursor-approve.yml(+12 -1)📝
.github/workflows/cursor-review.yml(+13 -0)📝
docs/callers/cursor-approve.md(+8 -0)📝
docs/callers/cursor-review.md(+6 -0)📄 Description
ELI-5
When cursor-review hands approval off to cursor-approve (
defer_approval: true), cursor-approve is also meant to tidy up: it closes the bot's own low-severity review threads. A delta round can leave some above-threshold findings marked "outside this round's changes: not blocking". cursor-review's own approval already ignored those marked threads when deciding whether to close the others. cursor-approve did not, because it had no way to know whether that round really ran asdelta. So one marked thread kept every thread open. This PR passes the round's real scope from cursor-review to cursor-approve, so both follow the same rule.What changed
.github/cursor-review/auto-approve.pydecidenow writes a new step output,approve_scope_effective: the scope the round actually gated under, afterresolve_scopeand #360's open-thread snapshot. It is written asfullfirst, and it is set to the resolved scope only oncedecide_gatereturns a decision. A newwithdraw_decision()sets it back tofullwhenever the gate is later downgraded tountrusted/capped(a failed POST, a head/base move, a lateneeds-human-reviewlabel, on both the posting path and the deferred path). So every fail-closed fallback (round 1, an unavailable, discarded or empty incremental block, an unparseable path, an unknown ledger, an unreadable list of earlier open threads) and every exit where no decision stands reportsfull.approve-externalgains--approve-scope(defaultfull). The value goes throughvalidate_scope, and onlydeltareachesresolve_eligible_threadsashonour_non_gating=True..github/workflows/cursor-review.yml: thepost-reviewjob forwards that step output, and a new workflow-level outputapprove_scope_effectiveexposes it. It falls back tofullwhen no decision ran..github/workflows/cursor-approve.yml: new optionalapprove_scopeinput (defaultfull, the fail-closed reading), passed as--approve-scope. The header comment documents it.docs/callers/cursor-approve.md(inputs table, prose, and the caller example now passesapprove_scope: ${{ needs.cursor-review.outputs.approve_scope_effective }}),docs/callers/cursor-review.md(new output row) and.github/cursor-review/README.md(defer_approvalrow).Behaviour is unchanged for every existing caller: they do not pass
approve_scope, so it isfull, which is today's behaviour where a marked thread blocks like any other.Judgment calls
--approve-scopevalue warns and resolves asfull; it does not fail the step. At that point the APPROVE has already been posted and re-checked. The existing invalid---thresholdpath also only warns there, and resolution is never fatal.fullis the stricter reading, so a bad value can never widen what gets resolved. When nothing above the threshold is open, threads are still resolved as before.deltaonly while a decision stands on adeltascope. cursor-approve's decide phase already runs only afterapprove_gate == 'pass'. The output still reportsfullon every no-decision or withdrawn exit (review finding r4204307345), so it never contradicts its documented contract.Self-review
scope == SCOPE_DELTAincmd_approve_external. It is safe becausevalidate_scopelowercases and strips the value and turns empty intofull, and theValueErrorbranch pins it tofull. Only an exactdeltaturns the exemption on. Even then a marked thread is never resolved itself (SKIP_NON_GATING), and an unmarked above-threshold thread still blocks everything (tested).resolve_eligible_threadshas exactly two callers,cmd_decide(already passed the flag) andcmd_approve_external(fixed here). The only workflow that runsapprove-externaliscursor-approve.yml.Provenance
Authored by: agent-work loop
Verified:
.github/cursor-reviewsuite green (1029),.github/cursor-approvesuite green,.github/workflow-pinssuite green (551, 43 skips are its own "Default-column comparison is off" opt-outs, which includes the declared-vs-documented input drift harness for the new input)approve-externalflag fails 10 subtests. Restoring the old write-on-resolve placement of thedecideoutput fails 5 subtests (head move, late label, failed POST, judge error, deferred head move)check_workflow_pins.py,check_agents_md.py, the org-repo-literal lint all passjob.workflow_shaunknown to that version, plus shellcheck info notes), and no CI job here runs actionlintNew tests
ApproveExternalResolveScopeTestruns approve-external through the real resolver with a marked Medium thread plus Low/Nit threads.delta(in any case, with surrounding whitespace) resolves the Low and Nit threads. Omitted, empty orfullresolves nothing. An unknown value warns and resolves asfull. Underdeltaan unmarked Medium thread still blocks.test_approve_scope_reaches_the_resolver_as_honour_non_gatingcovers the argument mapping for every value.test_scope_effective_is_full_unless_the_round_gated_on_deltachecks the decide output for a built delta, delta with REQUEST_CHANGES, a deferred delta pass, round 1, an unavailable block, a missing open-thread snapshot, explicitfull, two early validation exits, and the exits after the scope resolves where no decision stands.Deviations: the ticket's fix sketch implied "validate" might reject an invalid
--approve-scope; it warns and falls back tofullinstead (see Judgment calls)Residual
approve_scopedefaults tofull, so a cursor-approve caller sees no change until its decide-phase job passesapprove_scope: ${{ needs.cursor-review.outputs.approve_scope_effective }}. Its pin must also be bumped to a SHA that includes this change. The callers live in other repos, which this PR cannot edit. Each caller that runs cursor-review withapprove_scope: delta(the default) anddefer_approval: trueneeds a one-linewith:addition after its next bump.defer_approval: trueinto cursor-approve, so it is not confirmed thatapprove_scope_effectivecrossesjobs.post-review.outputs→ the workflow output → cursor-approve's input in a live run. The unit tests cover the Python on both ends, and the YAML wiring copies the existingapprove_gateoutput path.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.