ci(pr-review): label-only manual trigger, thin dispatcher (#23449)

Addresses the review feedback on #23418 (Paul + Copilot + cubic) and
switches the manual trigger from comments to **labels**, consistent with
the e2e labels.

## What changed
- **Manual reviews are label-driven** — add `pr-review-security` /
`pr-review-triage` / `pr-review-standard`. Labeling requires write
access (team-only). The **`/pr-review` comment trigger is removed.**
- Like the e2e labels, a check runs on every push **while its label is
present** (the orchestrator reads the PR's current labels each run) — so
`pr-review-standard` keeps the deep review current until removed.
- **Dispatcher is now dumb** — it forwards only `pr_number`. All
resolution + validation lives in the privileged orchestrator (Paul's
suggestion: it fetches PR metadata, incl. labels, there anyway). This
fixes the bot findings (regex allowlist bypass, `/pr-review`→standard
default, delimiter edge cases) at the source.
- `cancel-in-progress: true` (latest-push-wins, matching the previous
dispatcher).

Fires on non-draft PR events (the auto `security,triage` gate) and on
`pr-review-*` label adds.

## Depends on
A companion change to the privileged CI (reads labels +
resolves/validates checks) — merge that first; it's backward-compatible,
so nothing breaks in between.
This commit is contained in:
Charles Bochet
2026-07-28 19:19:33 +02:00
committed by GitHub
parent 4730542087
commit ea2de2dc2b

View File

@@ -1,67 +1,25 @@
name: PR Review Dispatch
run-name: "PR Review Dispatch (${{ github.event_name }})"
run-name: "PR Review Dispatch #${{ github.event.pull_request.number }}"
on:
pull_request_target:
types: [ready_for_review, synchronize, labeled]
issue_comment:
types: [created]
permissions: {}
concurrency:
group: pr-review-dispatch-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: false
group: pr-review-dispatch-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
dispatch:
if: >
(github.event_name == 'pull_request_target' && github.event.action != 'labeled' && github.event.pull_request.draft == false) ||
(github.event_name == 'pull_request_target' && github.event.action == 'labeled' && startsWith(github.event.label.name, 'pr-review-')) ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request != null && startsWith(github.event.comment.body, '/pr-review') && contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.comment.author_association))
github.event.pull_request.draft == false &&
(github.event.action != 'labeled' || startsWith(github.event.label.name, 'pr-review-'))
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Resolve target + checks
id: resolve
env:
EVENT_NAME: ${{ github.event_name }}
ACTION: ${{ github.event.action }}
PR_NUMBER: ${{ github.event.pull_request.number }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
LABEL_NAME: ${{ github.event.label.name }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
allowed="security triage standard"
is_valid() { printf '%s\n' $allowed | grep -qx "$1"; }
if [ "$EVENT_NAME" = "issue_comment" ]; then
NUM="$ISSUE_NUMBER"
CHECK=$(printf '%s' "$COMMENT_BODY" | sed -nE 's#^/pr-review[[:space:]]+([a-z]+).*#\1#p' | head -1)
CHECKS="${CHECK:-standard}"
elif [ "$ACTION" = "labeled" ]; then
NUM="$PR_NUMBER"
CHECKS="${LABEL_NAME#pr-review-}"
else
NUM="$PR_NUMBER"
CHECKS="security,triage"
fi
ok=true
IFS=',' read -ra parts <<< "$CHECKS"
for p in "${parts[@]}"; do is_valid "$p" || ok=false; done
if [ "$ok" = true ] && [ -n "$NUM" ]; then
echo "pr_number=$NUM" >> "$GITHUB_OUTPUT"
echo "checks=$CHECKS" >> "$GITHUB_OUTPUT"
echo "dispatch=true" >> "$GITHUB_OUTPUT"
else
echo "dispatch=false" >> "$GITHUB_OUTPUT"
fi
- name: Mint ci-privileged dispatch token
if: steps.resolve.outputs.dispatch == 'true'
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
@@ -71,12 +29,10 @@ jobs:
repositories: ci-privileged
permission-actions: write
- name: Dispatch to ci-privileged
if: steps.resolve.outputs.dispatch == 'true'
- name: Forward to PR review
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.resolve.outputs.pr_number }}
CHECKS: ${{ steps.resolve.outputs.checks }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh workflow run pr-review.yaml --repo twentyhq/ci-privileged --ref main \
-f pr_number="$PR_NUMBER" -f checks="$CHECKS"
-f pr_number="$PR_NUMBER"