Onboard a Repo to the PR Review Workflow
Use this skill when you are asked to add AI-powered PR review to a repo, or to upgrade an existing setup to the latest version of docker/docker-agent-action/.github/workflows/review-pr.yml.
How the reviewer is triggered — tell your team this
Primary trigger: add
docker-agentas a reviewer in the PR sidebar. Open a PR → Reviewers → typedocker-agent→ click. The review starts automatically and appears as a check run.To re-trigger: re-request a review from
docker-agentin the sidebar (click the refresh icon next to their name). This fires areview_requestedevent and starts a fresh review.
/reviewcomment: still works but is deprecated. Prefer the sidebar workflow.External / fork contributors: auto-review only runs on org members' own PRs. To review an external contributor's PR, an org member requests
docker-agentas a reviewer — the review is authorized by the requester, not the PR author.
Make sure to communicate this to contributors when onboarding a repo — it's the main daily interaction pattern and easy to miss if someone only reads the workflow YAML.
What you DON'T need to add — built-in protections
The reusable workflow handles all of the following internally. Do not add caller-side guards for these — they create maintenance burden without improving correctness or safety.
| Concern | How it's handled internally |
|---|---|
| Bot comment filtering | All jobs in the reusable workflow carry comprehensive if: conditions that skip docker-agent, docker-agent[bot], any Bot-type user, and comments containing <!-- docker-agent-review --> / <!-- docker-agent-review-reply --> HTML markers. |
| Org membership / authorization | A dedicated check-org-membership step runs before any review work begins. Auto-review verifies the PR author; a requested review verifies the requester (so a maintainer can pull an external contributor's PR into review); comment paths verify the commenter. Callers never need their own author_association checks. |
| PR vs issue comment disambiguation | The reusable workflow checks github.event.issue.pull_request internally. Plain issue comments on non-PR issues are ignored automatically. |
| Draft PR skipping | Handled internally — draft PRs are not reviewed. |
| Concurrent review guard | A cache-based lock (pr-review-lock-<repo>-<pr>-*) prevents duplicate reviews from racing on the same PR. |
The one thing callers ARE responsible for
The fork vs same-repo distinction is the caller's responsibility, because it determines the event path:
- Same-repo PRs → use the 1-workflow pattern (events have full OIDC/secret access directly).
- Fork PRs → use the 2-workflow pattern (trigger artifact →
workflow_runhandler).
The reusable workflow uses the presence of trigger-run-id to detect which path it's on. The canonical YAML in sections 4a and 4b below (without extra if: guards) is the recommended setup.
Note on optional optimizations: some teams add
author_associationchecks or bot-login filters on their calling workflow's jobif:to save Actions minutes by skipping the job entirely before it even calls the reusable workflow. This is a valid cost optimization, but it is not required for correctness or security. When in doubt, omit them — the simpler YAML is easier to audit and maintain.
1. Determine Which Pattern to Use
Check the repo's contribution guidelines and GitHub settings:
- Does the repo accept PRs from forks? (open-source repos, cross-org contributions,
CONTRIBUTING.mdmentions fork workflow) → use the 2-workflow (fork) pattern. - PRs only from branches within the same repo? (private repos, internal teams, branch-protection-only) → use the 1-workflow (same-repo) pattern.
When in doubt, check recent PRs: if any originate from a fork (author:fork or head.repo.fork == true), use the 2-workflow pattern.
2. Determine the Version to Use
Replace @VERSION in every workflow YAML below with the latest release tag. As of this writing the latest release is v2.0.0. Always verify:
gh release list --repo docker/docker-agent-action --limit 5
Use @main only for bleeding-edge / pre-release testing.
3. Choose a Trigger Mode
The pull_request event types control how often reviews run. Pick one mode and apply it to the trigger section of the workflow(s) below.
Mode B — recommended default (reviews on open, ready, and explicit re-request only):
pull_request:
types: [opened, ready_for_review, review_requested]
Mode A — continuous re-review on every push (adds synchronize):
pull_request:
types: [opened, ready_for_review, synchronize, review_requested]
Mode A costs more workflow minutes. Opt in only if the team wants the reviewer to automatically re-examine every push to the PR branch.
The examples below use Mode B.
4a. Same-Repo PRs — 1-Workflow Pattern
Create one file: .github/workflows/pr-review.yml
name: PR Review
on:
pull_request:
types: [ready_for_review, opened, review_requested]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
permissions:
contents: read
jobs:
review:
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@VERSION
permissions:
contents: read # Read repository files and PR diffs
pull-requests: write # Post review comments
issues: write # Create security incident issues if secrets detected
checks: write # (Optional) Show review progress as a check run
id-token: write # Required for OIDC authentication to AWS Secrets Manager
actions: write # Cache read/write for review-lock deduplication and binary cache
All three events (pull_request, issue_comment, pull_request_review_comment) have full OIDC/secret access for same-repo PRs, so the reusable workflow handles everything directly.
Replace @VERSION with the tag from Step 2 (e.g. @v2.0.0).
4b. Fork PRs — 2-Workflow Pattern
Fork PRs run under GitHub's security restrictions: pull_request and pull_request_review_comment events get read-only tokens, no secrets, and no OIDC. The solution is a lightweight "trigger" workflow that saves only untrusted locator hints as an artifact; a workflow_run handler then picks it up with full permissions.
File 1: .github/workflows/pr-review-trigger.yml
Lightweight — no secrets needed, runs in the fork's context:
name: PR Review - Trigger
on:
pull_request:
types: [ready_for_review, opened, review_requested]
pull_request_review_comment:
types: [created]
permissions: {}
jobs:
save-context:
# A review request for anyone other than docker-agent must not fan out to a review.
if: >
github.event_name != 'pull_request' ||
github.event.action != 'review_requested' ||
github.event.requested_reviewer.login == 'docker-agent'
runs-on: ubuntu-latest
steps:
- name: Save event context
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
COMMENT_ID: ${{ github.event.comment.id }}
run: |
mkdir -p context
printf '%s' "${{ github.event_name }}" > context/event_name.txt
printf '%s' "$PR_NUMBER" > context/pr_number.txt
if [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then
printf '%s' "$COMMENT_ID" > context/comment_id.txt
fi
- name: Upload context
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pr-review-context
path: context/
retention-days: 1
File 2: .github/workflows/pr-review.yml
Full-permissions handler — calls the reusable workflow:
name: PR Review
on:
issue_comment:
types: [created]
workflow_run:
workflows: ["PR Review - Trigger"]
types: [completed]
permissions:
contents: read
jobs:
review:
if: |
(github.event_name == 'issue_comment' &&
github.event.comment.user.login != 'docker-agent' &&
github.event.comment.user.login != 'docker-agent[bot]' &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.comment.body, '<!-- docker-agent-review -->') &&
!contains(github.event.comment.body, '<!-- docker-agent-review-reply -->')) ||
github.event.workflow_run.conclusion == 'success'
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@VERSION
permissions:
contents: read # Read repository files and PR diffs
pull-requests: write # Post review comments
issues: write # Create security incident issues if secrets detected
checks: write # (Optional) Show review progress as a check run
id-token: write # Required for OIDC authentication to AWS Secrets Manager
actions: write # Cache read/write for review-lock deduplication and binary cache
with:
trigger-run-id: ${{ github.event_name == 'workflow_run' && format('{0}', github.event.workflow_run.id) || '' }}
Replace @VERSION in the uses: line with the tag from Step 2 (e.g. @v2.0.0).
How the two workflows interact
pull_request (opened / ready_for_review / review_requested)
→ pr-review-trigger.yml (saves context artifact, no secrets needed)
→ completes
→ workflow_run fires
→ pr-review.yml (downloads artifact, full OIDC, runs review)
pull_request_review_comment
→ pr-review-trigger.yml (saves context artifact)
→ workflow_run fires
→ pr-review.yml (routes to reply-to-feedback or reply-to-mention)
/review comment –OR– @docker-agent mention
→ pr-review.yml directly (issue_comment always has full permissions)
issue_comment always has full permissions regardless of fork status, so /review commands and @docker-agent mentions bypass the trigger workflow entirely.
Trigger-artifact upgrade and rollback compatibility
The trigger artifact is an untrusted locator. The reusable workflow fetches all authoritative
PR and comment data from GitHub; it only uses event_name.txt, pr_number.txt, and (for review
comments) comment_id.txt to locate that data.
| Trigger artifact producer | Reusable workflow consumer | Supported? | Required order / behavior |
|---|---|---|---|
| New minimized locator artifact | Updated reusable workflow | Yes | Normal target state. The resolver uses the locator files and server-fetches authoritative values. |
| Legacy full-context artifact | Updated reusable workflow | Yes | Safe rollout bridge. The resolver extracts only the comment ID from legacy comment.json, then server-fetches authoritative values. |
| New minimized locator artifact | Older reusable workflow | Not guaranteed | Upgrade the reusable workflow before minimizing the trigger artifact. Roll back by restoring the legacy artifact format until the consumer is upgraded. |
For this repository's self-review workflow, the currently pinned v2.0.4 self-reference predates
pr-head-sha and pr-base-sha; it does not accept those immutable SHA inputs. After releasing the
updated reusable workflow, bump every internal pin and its version comment before relying on
dogfooding for this path. The current pin must not be treated as coverage of immutable-SHA input
wiring.
5. Upgrade Checklist
For repos that already have the workflows, verify each item:
- Version/tag is current — compare the
@VERSIONinuses:against the latest release fromgh release list --repo docker/docker-agent-action --limit 1. Update if behind. - All required permissions are present —
contents: read,pull-requests: write,issues: write,id-token: write,actions: write. Missing any of these causes silent failures or OIDC/artifact errors. Note: missingactions: writespecifically causes a 403 when the reusable workflow tries to store binary cache or upload/download artifacts (cache write operations requirewrite; artifact download requires onlyread). -
checks: writeis present (optional but recommended) — without it the review won't appear as a check run on the PR. - Bot-filter
ifcondition is correct — the condition must filter outdocker-agent,docker-agent[bot], anyBotuser type, and comments containing<!-- docker-agent-review -->or<!-- docker-agent-review-reply -->. A missing or incomplete filter causes infinite review loops. - Fork repos: reviewer-target gate is present — if
pull_request.review_requestedis enabled,save-contextmust run it only whengithub.event.requested_reviewer.login == 'docker-agent'. A request for a human, team, or other bot must leavesave-contextskipped and must not invoke the privilegedworkflow_runhandler; a request for exactlydocker-agentproceeds. - Fork artifact rollout order is safe — upgrade the reusable workflow before switching the trigger to the minimized locator artifact. New minimized artifacts with an older reusable workflow are not guaranteed to work; roll back by restoring the legacy artifact format until the consumer is upgraded.
- Repository self-review pins are upgraded after release — the currently pinned
v2.0.4self-reference predates immutablepr-head-sha/pr-base-shainputs. Bump its SHA and version comment after release; do not claim the current dogfood pin exercises immutable-SHA input wiring. - Fork repos: trigger workflow has the artifact upload step — the
actions/upload-artifactstep must be present inpr-review-trigger.yml, pinned to a specific commit SHA (not just a tag). Without it theworkflow_runhandler has no artifact to download. - Fork repos:
trigger-run-idinput is wired correctly — must be${{ github.event_name == 'workflow_run' && format('{0}', github.event.workflow_run.id) || '' }}. An empty string is safe forissue_commentevents; the reusable workflow handles both paths. - Fork repos:
workflow_run.workflowsarray matches the trigger workflow name exactly — the string"PR Review - Trigger"(or whatever you named it) must match thename:field inpr-review-trigger.ymlcharacter-for-character.
6. Common Mistakes and Troubleshooting
OIDC auth fails / no credentials available
Cause: id-token: write permission is missing from the job's permissions block.
Fix: Add id-token: write to the permissions block on the review job (not just the top-level workflow permissions).
jobs:
review:
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@VERSION
permissions:
id-token: write # ← must be here
...
Artifact download fails with 403
Cause: actions: write is missing from the pr-review.yml job permissions. This permission is required by the reusable workflow for artifact operations on all setups, not just fork repos.
Fix: Add actions: write to the permissions block on the review job in pr-review.yml.
Infinite review loop
Cause: The if condition on the save-context job (trigger workflow) or the review job is not filtering bot comments. The agent posts a comment → that fires an issue_comment or pull_request_review_comment event → the workflow triggers again → repeat.
Fix: Ensure the if condition filters all of:
github.event.comment.user.login != 'docker-agent'github.event.comment.user.login != 'docker-agent[bot]'github.event.comment.user.type != 'Bot'!contains(github.event.comment.body, '<!-- docker-agent-review -->')!contains(github.event.comment.body, '<!-- docker-agent-review-reply -->')
workflow_run never fires
Cause: The workflows: array in pr-review.yml's workflow_run trigger doesn't match the name: field of the trigger workflow.
Fix: Check that the string in workflows: ["PR Review - Trigger"] matches exactly the name: field at the top of pr-review-trigger.yml. Rename one to match the other.
Reviews don't run on fork PRs at all
Cause: The trigger workflow (pr-review-trigger.yml) is missing, or its pull_request trigger types don't include opened / ready_for_review / review_requested.
Fix: Confirm pr-review-trigger.yml exists in .github/workflows/ on the default branch and that its on.pull_request.types list matches the desired trigger mode.
Review doesn't appear as a check run
Cause: checks: write permission is absent.
Fix: Add checks: write to the job permissions block. This is optional but strongly recommended so the review progress is visible in the PR's Checks tab.
7. Audit: Validate All Consuming Repos in the Docker Org
Use this procedure when asked to audit, validate, or report on the health of the PR reviewer setup across repos in the Docker org.
Step 1 — Discover consuming repos
Search GitHub for all files in the docker org that reference the reusable workflow:
gh search code "docker/docker-agent-action/.github/workflows/review-pr.yml" \
--owner docker \
--filename "*.yml" \
--json repository,path \
--limit 100
This returns a list of (repository, path) pairs. For each unique repository, fetch the actual workflow file(s):
# Example: fetch a workflow file from a discovered repo
gh api repos/docker/<repo>/contents/.github/workflows/pr-review.yml \
--jq '.content' | base64 -d
Also check for a trigger workflow in fork setups:
gh api repos/docker/<repo>/contents/.github/workflows/pr-review-trigger.yml \
--jq '.content' | base64 -d 2>/dev/null || echo "no trigger workflow"
Check whether the repo accepts fork PRs (to validate the correct pattern is in use):
gh api repos/docker/<repo> --jq '{allow_forking, visibility, fork}'
Also check for open PRs from forks as a real-world signal:
gh pr list --repo docker/<repo> --json headRepository --limit 50 \
--jq '[.[] | select(.headRepository.isFork == true)] | length'
Step 2 — Validate each repo
For each repo discovered, run through this checklist. Note every issue found.
Version / SHA currency
# Get the latest release tag
LATEST=$(gh release view --repo docker/docker-agent-action --json tagName --jq '.tagName')
echo "Latest: $LATEST"
# Extract the version in use from the workflow file
grep "review-pr.yml@" .github/workflows/pr-review.yml
- The
uses:line ends with@<LATEST>(e.g.@v2.0.0). Flag if behind.
Fork pattern correctness
- If the repo has fork PRs (or
allow_forking: trueand is public), it must use the 2-workflow pattern (pr-review.yml+pr-review-trigger.yml). - If the repo is private or fork PRs are disabled, the 1-workflow pattern is correct and sufficient.
- Flag if a public/open-source repo is using the 1-workflow pattern without confirming forks are disabled.
Required permissions
Check the permissions: block on the review job in pr-review.yml:
-
contents: read -
pull-requests: write -
issues: write -
id-token: write← OIDC; missing this breaks all credential fetching -
checks: write← optional but strongly recommended -
actions: write← required for all setups (reusable workflow uses it for artifact operations)
Trigger types
Check on.pull_request.types in pr-review.yml (or pr-review-trigger.yml for fork setups):
- Includes
review_requested— the primary trigger (sidebar reviewer UX) - Includes
ready_for_review - Includes
opened
Unnecessary caller-side if: guards
The reusable workflow handles all safety checks internally. Flag any of the following as unnecessary (safe to remove, not a correctness issue):
-
author_associationchecks on the calling job - Bot-login filters (
github.event.comment.user.login != 'docker-agent', etc.) on any job in the calling workflow -
github.event.issue.pull_requestchecks on the calling job - Draft PR
if:guards on the calling job
Fork setup specifics
For repos using the 2-workflow pattern, additionally check:
-
trigger-run-idinput is wired as:trigger-run-id: ${{ github.event_name == 'workflow_run' && format('{0}', github.event.workflow_run.id) || '' }} -
workflow_run.workflowsinpr-review.ymlmatches thename:field inpr-review-trigger.ymlexactly (character-for-character, including capitalisation and spaces) -
actions/upload-artifactin the trigger workflow is pinned to a full commit SHA (not just a tag like@v4). Check against the current pinned SHA in this repo's own trigger workflow:grep "upload-artifact" /workspace/.github/workflows/self-review-pr-trigger.yml
Step 3 — Produce a summary report
Group findings by status. Use this format:
## PR Reviewer Workflow Audit — docker org
Checked: <date> | Latest release: <tag>
### ✅ Compliant (<N> repos)
- docker/<repo> — <pattern>, <version>
- ...
### ⚠️ Needs update (<N> repos)
- docker/<repo>
- Version outdated: using @v1.x.x, latest is @v2.0.0
- Missing permission: `checks: write`
- <other issues>
- ...
### ❌ Critical problems (<N> repos)
- docker/<repo>
- <description of broken config or missing workflows>
- ...
For each issue in the ⚠️ and ❌ buckets, note the relevant section of this skill where the fix is documented.
Step 4 — Remediation
When asked to fix issues found in the audit:
- For each affected repo, open a PR against that repo with the corrections.
- One PR per repo (batch all fixes for a repo into a single PR).
- In the PR description, list every issue found and how it was fixed.
- Reference the relevant section of this skill (
## 5. Upgrade Checklist,## 4a,## 4b, etc.) for context. - Assign the PR to the repo owner or use
--assignee @meif no clear owner.
Don't fix what isn't broken. Unnecessary
if:guards (author_association checks, bot filters in the main review job) are safe to remove but are not critical. Only include their removal in a PR if you are already making other changes to that file — don't open a PR solely to remove optional guards.