Skip to content

fix(ci): the last-push-approval check reads a run's actor, not its triggering_actor - #3479

Closed
cagataycali wants to merge 3 commits into
strands-labs:mainfrom
cagataycali:fix/last-push-approval-reads-actor
Closed

cagataycali wants to merge 3 commits into
strands-labs:mainfrom
cagataycali:fix/last-push-approval-reads-actor

Conversation

@cagataycali

Copy link
Copy Markdown
Member

Closes #3452.

What

resolve_pusher read triggering_actor from the workflow runs on the head sha. That field names the account behind the latest attempt, so GitHub rewrites it when a maintainer approves a held run or re-runs one. actor is the account whose event created the run, which for a push / pull_request run is the pusher.

Why

Every run on a first-time contributor's fork starts at action_required, so approving them is a maintainer's ordinary first act -- and the check then named that maintainer as the pusher of a branch in a repository they cannot push to. Reading the same pull_request runs by both fields:

pull request actor triggering_actor
#3448, #3427, #3400 (runs held, then approved) shipitfast cagataycali
#3467 (runs never held) shipitfast shipitfast

The event filter does not help: the approval re-attributes the existing pull_request run rather than creating a new one. Once that maintainer approved the pull request, this check read pusher-only-approval and check_merge_blockers.py reported the pull request as owed to "a reviewer other than the pusher" -- while require_last_push_approval, which reads the real pusher, was satisfied and the pull request was mergeable.

--all-open over this repository, same moment, before and after:

pull request pushed by (before) pushed by (after)
#3400 cagataycali shipitfast
#3427 cagataycali shipitfast
#3448 cagataycali shipitfast
#3205, #3343, #3458, #3467, #3470, #3477 unchanged unchanged

Nothing the old field got right is lost

The event filter stays, and stays load-bearing -- a pull_request_review run's actor is the reviewer. actor agrees with triggering_actor on every case this check already pinned (#1894, #1920, #1722, #1035 and #1921's own), including the two heads whose commit.author.login does not answer at all.

Tests

test_approving_a_held_run_does_not_rename_the_pusher builds a run whose two fields differ: 1 failed, 52 passed pre-fix (assert 'cagataycali' == 'shipitfast'), 53 passed after. The other 52 pass on either field, because run fixtures now go through one workflow_run helper that sets both -- so they pin the pusher rather than a field name.

Full tests/ 50,726 passed / 308 skipped / 0 failed; ruff check, ruff format --check and mypy clean over 1,944 files.

The module docstring, AGENTS.md > PR Workflow and the rendered report row carry the correction. One further site is left untouched: the actions: read comment in .github/workflows/last-push-approval.yml:92 still says "its triggering_actor is correct" and needs the same one-line edit, which a branch pushed without workflow scope cannot carry.

…iggering_actor

resolve_pusher read triggering_actor from the workflow runs on the head sha.
That field names the account behind the latest attempt, so GitHub rewrites it
when a maintainer approves a held run or re-runs one; actor is the account whose
event created the run, which for a push/pull_request run is the pusher.

Every run on a first-time contributor's fork starts at action_required, so
approving them is a maintainer's ordinary first act -- and the check then named
that maintainer as the pusher of a branch in a repository they cannot push to.
Measured on the same nine pull_request runs of strands-labs#3448, head b3d2233:
actor shipitfast, triggering_actor cagataycali. strands-labs#3467, whose runs were never
held, reads shipitfast in both. The event filter does not help, because the
approval re-attributes the existing pull_request run rather than creating a new
one under a different event.

Once that maintainer approved the pull request the check read
pusher-only-approval, and check_merge_blockers.py reported the pull request as
owed to a reviewer other than the pusher, while require_last_push_approval --
which reads the real pusher -- was satisfied and the pull request was mergeable.

The event filter stays, and stays load-bearing: a pull_request_review run's
actor is the reviewer. actor agrees with triggering_actor on every case this
check already pinned (strands-labs#1894, strands-labs#1920, strands-labs#1722, strands-labs#1035, strands-labs#1921's own), including the
two heads whose commit metadata does not answer, so nothing the old field got
right is lost. The module docstring, AGENTS.md > PR Workflow, the workflow's
permissions comment and the test fixtures carry the correction; the fixtures now
build runs through one helper that sets both fields, so they pin the pusher
rather than a field name.

Closes strands-labs#3452
@cagataycali
cagataycali enabled auto-merge (squash) September 11, 2026 00:29
@cagataycali

Copy link
Copy Markdown
Member Author

Duplicate of #3478, which is the same one-field fix and is already approved. Closing in favour of it.

One difference worth keeping in mind for later: here the run fixtures went through a single helper that sets both fields, so the pre-existing pins stay field-agnostic (pre-fix split was 1 failed / 52 passed rather than partly-renamed fixtures). Not worth a push onto an approved branch.

auto-merge was automatically disabled September 11, 2026 00:33

Pull request was closed

@yinsong1986 yinsong1986 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR fixes scripts/check_last_push_approval.py to read a workflow run's actor (the account whose event created the run, i.e. the pusher for push/pull_request runs) instead of triggering_actor (the account behind the latest attempt, which GitHub rewrites when a maintainer approves a held run or re-runs one). The old read misattributed the pusher to the approving maintainer on first-time-contributor fork PRs, producing false pusher-only-approval findings on PRs that GitHub's own require_last_push_approval rule was willing to merge. The change is a single field swap with the same null-guard, event filter, and newest-run-wins tie-break; the script is reporting-only (non-gating), so there is no one-way door. Evidence is measured in both directions (three misattributed PRs flip to the real pusher, six unchanged, and actor agrees with triggering_actor on all five previously pinned cases including the two whose commit metadata declines to answer).

What's good

  • Regression test (test_approving_a_held_run_does_not_rename_the_pusher) pins the exact divergence and fails pre-fix, per the AGENTS.md pin-every-reviewed-fix rule.
  • The new workflow_run fixture helper sets both attribution fields, so the pre-existing 52 tests now pin the pusher rather than a field name.
  • Changelog fragment is correctly named 3479-<slug>.md, and the AGENTS.md tables plus the grader test (test_last_push_rule_names_the_update_branch_button.py) are updated in the same change so the doc and its pin cannot drift.
  • The one deliberately untouched site (the triggering_actor comment in last-push-approval.yml:92) is disclosed with a sound reason: a branch pushed without workflow scope cannot carry a workflow-file edit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check_last_push_approval names the maintainer who approved a fork's held CI runs as the pusher

3 participants