mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #410] [MERGED] feat(cursor-review): accept pull_request_target, pin PR checkout refs, add approver_environment #412
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#412
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/410
Author: @mattmillerai
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/11/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-20107-cursor-review-pull-request-target📝 Commits (3)
78024b9feat(cursor-review): accept pull_request_target, pin PR checkout refs, add approver_environmenta653bf0docs(cursor-review): align approve callers with the pull_request_target token rule03a5220fix(cursor-review): approve only from pull_request_target, pin PR checkouts to head.sha📊 Changes
6 files changed (+339 additions, -37 deletions)
View changed files
📝
.github/cursor-review/README.md(+1 -0)📝
.github/cursor-review/tests/test_workflow_job_isolation.py(+72 -0)📝
.github/workflows/cursor-approve.yml(+37 -3)📝
.github/workflows/cursor-review.yml(+127 -6)📝
docs/callers/cursor-approve.md(+21 -1)📝
docs/callers/cursor-review.md(+81 -27)📄 Description
ELI-5
Today, a PR author who can push a branch can also edit the workflow that holds the approver token, because
pull_requestruns the caller workflow from the PR head. This change lets cursor-review and cursor-approve run frompull_request_target, where the caller comes from the base branch. It also adds anapprover_environmentinput so the token can live in a base-branch-only environment. The change is additive: existingpull_requestcallers still run.What changed
pull_request_targetis accepted everywherepull_requestwas tested: the workflow-level concurrency expression (run_without_labelbranch), thedismiss-stale-approvalif:, and the blocking-gate error text.EVENT_NAMEis used only in that message. cursor-approve reads onlygithub.event.pull_request.*(number, head sha, base ref), and that payload is present under both events.Checkout PR reposteps (review,judge-direct,consolidate) now pingithub.event.pull_request.head.sha, asreview-anthropic-directanddiff-sizealready did. The merge ref was mutable between jobs and missing on conflicted PRs, whichpull_request_targetstill runs on.approver_environment(string, default''): declared on exactlypost-review,dismiss-stale-approval,auto-retry(cursor-review) andapprove(cursor-approve), and only underpull_request_target(environment: ${{ github.event_name == 'pull_request_target' && inputs.approver_environment || '' }}). Any other event runs onrefs/pull/<n>/merge, which a base-branch-only deployment policy would reject before a step runs.pull_request_targetmay approve asAPPROVER_TOKEN. On any other event with the token set (pull_request,pull_request_review_thread, …), the token steps print an::error::and approve nothing. The decision keeps the token identity under--defer-approval, so it still withdraws that identity's own earlier approvals. cursor-approve outputswithdraw_onlyand runs onlyWithdraw approvals.auto-retrydrops to the fallback identity. Withapprover_environmentset, a non-target run has no token and posts no approval under any identity. Underpull_request_targetwith noapprover_environment, the steps warn. No posting job turns red.pull_request_targeta Dependabot-authored PR is skipped, because it would otherwise get the repo's real secrets.### Trust modelis now a real heading, so#trust-modelresolves, and it is rewritten to thepull_request_target+ environment shape, with the fork-skip and Dependabot notes. Also updated: the "drop the trigger" residual, the cursor-approve caller example, both inputs tables and the cursor-review.yml header example. Review round: the cursor-approve caller example addsedited(so a base retarget reachesdismiss-stale-approval) with its own concurrency group; the auto-approve example moves topull_request_target+approver_environment; and the trust model tells public repositories to allowpull_request_targetthrough an Actions event policy before switching (GitHub's default blocks it, enforced from November 2, 2026). Panel round: the trust model says thread resolution does not approve in this shape, and documents the base-branch Actions cache scope. The cursor-approve section explains why it has nopull_request_review_threadtrigger.test_workflow_job_isolation.py. One checks that the conditional environment key appears only on the APPROVER_TOKEN jobs (in both workflows) and never on a PR-checkout job. The other checks that every PR checkout pinsref: head.sha.Probe: is an empty
environment:accepted?Yes. I ran this on a scratch branch (
probe/…-empty-environment, deleted afterwards) with apush-triggered workflow:inlinejob:environment: ${{ github.event_name == 'never' && 'x' || '' }}→ success.called / token-job: a local reusable whose job setsenvironment: ${{ inputs.env_name }}, called withenv_name: ''→ success.["copilot"], so no''environment was auto-created), anddeployments?ref=<probe branch>returned 0. So thedefault: ''design holds, and neither fallback from the spec (adefault: 'cursor-approver'or a split job) was needed.Judgment calls
post-review's decision,dismiss-stale-approvaland cursor-approve keep the token login only to withdraw, and to request changes in the decision. They never approve. Onlyauto-retrydrops to the fallback, because a relabel approves nothing.pull_requestcallers that passAPPROVER_TOKEN. Their runs still go green, but no auto-approval is posted until the caller moves topull_request_target(on any event, including thread resolution). The decision card says "approval is left to cursor-approve's axes" in that case, because it reuses thedefer_approvalpath. The::error::annotation gives the real reason.pull_request_review_threadcaller comes from the PR, so it may not approve as the token. The blocking caller's approval now waits for the nextpull_request_targetevent. Routing thread resolution through a trusted trigger would be a separate design.gate, not left to the caller, underpull_request_targetonly.pull_request_target-without-environment warning is worded for that case rather than copying thepull_requesterror verbatim, because "under a pull_request trigger" would be false there.Residual
environment:, it receives the caller's environment secretAPPROVER_TOKEN. GitHub's reusable-workflow docs say "the environment secret will be used". I have not run that end to end (I created no environment or secret). The first caller migration should confirm the approval posts under the token identity.pull_request_targetor review_thread run. The guard and environment behavior is checked by static tests and actionlint only.Provenance
.github/cursor-review/tests(1460) OK,.github/cursor-approve/testsOK,.github/workflow-pins/testsOK (43 skipped)check_workflow_pins.pyOK,check-org-repo-literals.shOK,check_agents_md.pypassed (2 warnings, pre-existing)main's existing setenvironment:probe: run 38098449898, both jobs green, no environment or deployment createdpull_request_targetdefault-policy dates checked against GitHub's "Securely using pull_request_target" docspull_request_targetcaller with a real environment secret🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.