Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 21 additions & 20 deletions .github/workflows/pr-review-panel.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 41 additions & 18 deletions .github/workflows/pr-review-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,42 @@ description: Multi-persona expert panel review of labelled PRs, posting a single

# Triggers (cost-gated, fork-safe, GHES-compatible):
#
# 1. pull_request: only when a maintainer applies the `panel-review` label.
# We deliberately do NOT subscribe to `synchronize` -- previous behaviour
# re-ran the panel on every push to a labelled PR, which is wasteful and
# indistinguishable from a DoS on agent quota. Re-apply the label
# (remove + add) to re-run after addressing findings.
# 1. pull_request_target: fires when a label is applied. We use _target
# (not plain pull_request) so that fork PRs run in the BASE repo
# context with full secrets (COPILOT_GITHUB_TOKEN etc.). The label
# name is filtered inside the prompt (Step 0) -- gh-aw does not
# expose `names:` on pull_request_target.
#
# 2. workflow_dispatch: manual trigger taking a PR number. This is the
# only path that works for fork PRs on GitHub.com and GHES, because
# `pull_request` from forks does NOT pass repository secrets
# (COPILOT_GITHUB_TOKEN etc.), and gh-aw blocks `pull_request_target`
# on public repos. workflow_dispatch always runs in the base/trusted
# context with full secrets, regardless of where the PR head lives.
# Why pull_request_target is safe here despite the well-known
# "pwn-request" pattern:
# - permissions are read-only (no write to contents / actions)
# - we never `actions/checkout` the PR head; only `gh pr view` /
# `gh pr diff` which return inert text
# - imports are pinned to microsoft/apm#main (panel skill +
# persona definitions are trusted, not from the PR)
# - the only write surface is safe-outputs.add-comment (max 1)
# - `roles: [admin, maintainer, write]` ensures only repo
# maintainers can trigger -- matches the trust model that
# applying the `panel-review` label requires write access.
#
# `forks: ["*"]` is retained so the label path also works against fork
# PRs in private/enterprise repos where secrets DO pass to fork PRs
# (per-org setting). On microsoft/apm public, the dispatch path is the
# reliable fork route.
# `synchronize` is intentionally NOT subscribed: previous behaviour
# re-ran the panel on every push to a labelled PR, which burned
# agent quota. Re-apply the label (remove + add) to re-run after
# addressing findings.
#
# 2. workflow_dispatch: manual fallback. Reads YAML from the dispatched
# ref (default main) and accepts any PR number. Useful if a
# maintainer needs to re-run without touching labels.
on:
pull_request:
pull_request_target:
types: [labeled]
names: [panel-review]
forks: ["*"]
workflow_dispatch:
inputs:
pr_number:
description: "Pull request number to review (works for fork PRs)"
required: true
type: string
roles: [admin, maintainer, write]

# Agent job runs READ-ONLY. Safe-output jobs are auto-granted scoped write.
permissions:
Expand Down Expand Up @@ -75,6 +83,21 @@ timeout-minutes: 30
You are orchestrating the **apm-review-panel** skill against pull request
**#${{ github.event.pull_request.number || inputs.pr_number }}** in `${{ github.repository }}`.

## Step 0: Label-name guard (skip when irrelevant)

`pull_request_target: types: [labeled]` fires for ANY label change. Bail
immediately unless the triggering label is `panel-review` (or this is a
manual `workflow_dispatch`):

```bash
EVENT="${{ github.event_name }}"
LABEL="$(jq -r '.label.name // ""' "$GITHUB_EVENT_PATH")"
if [ "$EVENT" = "pull_request_target" ] && [ "$LABEL" != "panel-review" ]; then
echo "Triggering label is '$LABEL' (not 'panel-review'); exiting cleanly."
exit 0
fi
```

## Step 1: Load the panel skill

The APM bundle has been unpacked into the runner workspace by the `apm` pre-job.
Expand Down
Loading