fix(actions)!: require merged PR to bypass fork PR approval gate (#38010) (#38041)

Backport #38010 by @bircni

`ifNeedApproval` in `services/actions/notifier_helper.go` decided
whether a
fork PR's workflow run had to wait for maintainer approval. The bypass
clause
counted any prior `approved_by > 0` run for `(repo_id,
trigger_user_id)`, so
the very first Approve-and-run click on a contributor's fork PR
permanently
trusted that user for every future fork PR in the same repository —
including
PRs whose only change is the workflow YAML itself.

Approving a workflow *run* is not the same as merging *code*. This
change
aligns the gate with GitHub Actions' first-time-contributor model: trust
is
granted only after the user has had a pull request merged in the repo.

## Behavior change

- **Before**: one approval = permanent trust for that user in that repo.
- **After**: every fork PR is gated until the contributor has at least
one
  merged PR in the repo.

Existing already-approved runs and merged PRs continue to work; only the
trust criterion for *future* fork PRs changes. Maintainers who rely on
the
implicit "approve once" trust will see the approval banner reappear
until
they merge a PR from that contributor.

---------

Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
Giteabot
2026-06-09 15:53:42 +02:00
committed by GitHub
co-authored by bircni
parent 3b705738ab
commit e107498f3b
5 changed files with 239 additions and 18 deletions
-4
View File
@@ -70,7 +70,6 @@ type FindRunOptions struct {
Ref string // the commit/tag/… that caused this workflow
TriggerUserID int64
TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true
Status []Status
ConcurrencyGroup string
CommitSHA string
@@ -87,9 +86,6 @@ func (opts FindRunOptions) ToConds() builder.Cond {
if opts.TriggerUserID > 0 {
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID})
}
if opts.Approved {
cond = cond.And(builder.Gt{"`action_run`.approved_by": 0})
}
if len(opts.Status) > 0 {
cond = cond.And(builder.In("`action_run`.status", opts.Status))
}