PR Reviewer Skill
Review pull requests by validating code quality, running CI checks, starting the app in Docker, smoke-testing endpoints, analyzing code for bugs, verifying acceptance criteria, and validating the original story intent. Posts a structured review on GitHub, then auto-fixes all findings and CI failures, tests fixes locally, commits and pushes to the PR branch, and sets labels based on results.
Shell State
Each shell command runs in a fresh shell. Store persistent values in /tmp/pr-review-<PR>.state and source it at the start of every command. Every command MUST start with:
source /tmp/pr-review-<PR_NUMBER>.state && cd "$REPO_ROOT"
Workflow
Phase 0: Pre-flight Safety
Parse input, record state, write cleanup script (single command):
PR_NUMBER=<PR_NUMBER> ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD) REPO_ROOT=$(git rev-parse --show-toplevel) COMPOSE_PROJECT="pr-review-${PR_NUMBER}" STATE_FILE="/tmp/pr-review-${PR_NUMBER}.state" VOLUMES_FILE="/tmp/pr-review-${PR_NUMBER}-volumes.yml" COMPOSE_CMD="docker compose -f docker/docker-compose.dev.yml -f ${VOLUMES_FILE} --project-name ${COMPOSE_PROJECT}" echo "PR_NUMBER=${PR_NUMBER}" > "$STATE_FILE" echo "ORIGINAL_BRANCH=${ORIGINAL_BRANCH}" >> "$STATE_FILE" echo "REPO_ROOT=${REPO_ROOT}" >> "$STATE_FILE" echo "COMPOSE_PROJECT=${COMPOSE_PROJECT}" >> "$STATE_FILE" echo "COMPOSE_CMD=\"${COMPOSE_CMD}\"" >> "$STATE_FILE" echo "VOLUMES_FILE=${VOLUMES_FILE}" >> "$STATE_FILE" # Compose overlay to isolate named volumes from user's dev stack cat > "$VOLUMES_FILE" <<'VOLEOF' volumes: postgres-app-data: name: "PRPREFIX-pg-app" timescale-data: name: "PRPREFIX-ts" backend-data: name: "PRPREFIX-backend" timescale-backups: name: "PRPREFIX-ts-backup" VOLEOF sed -i.bak "s/PRPREFIX/pr-review-${PR_NUMBER}/g" "$VOLUMES_FILE" && rm -f "${VOLUMES_FILE}.bak" # Cleanup script cat > "/tmp/pr-review-${PR_NUMBER}-cleanup.sh" <<CLEANUP #!/bin/bash STATE_FILE="/tmp/pr-review-${PR_NUMBER}.state" source "\$STATE_FILE" 2>/dev/null || ORIGINAL_BRANCH="dev" docker compose -f docker/docker-compose.dev.yml -f /tmp/pr-review-${PR_NUMBER}-volumes.yml --project-name pr-review-${PR_NUMBER} down -v --remove-orphans 2>/dev/null || true cd "\${REPO_ROOT:-.}" 2>/dev/null git checkout "\$ORIGINAL_BRANCH" 2>/dev/null || git checkout dev 2>/dev/null || true rm -f "\$STATE_FILE" "/tmp/pr-review-${PR_NUMBER}-cleanup.sh" "/tmp/pr-review-${PR_NUMBER}-volumes.yml" "/tmp/pr-review-${PR_NUMBER}-comment.md" echo "Cleanup complete for PR #${PR_NUMBER}." CLEANUP chmod +x "/tmp/pr-review-${PR_NUMBER}-cleanup.sh"Check for dirty working tree: Abort if uncommitted changes exist.
Check for NO AI label on linked issues. If found, refuse to review.
Check diff size: Warn if > 3000 lines.
Check for port conflicts (3051, 5273, 5433). If blocked, set
DOCKER_BLOCKED=truein state file and skip Docker phases.Clean up orphaned review containers:
$COMPOSE_CMD down -v --remove-orphans
Phase 1: PR Context & Story Understanding
Fetch PR metadata:
gh pr view $PR_NUMBER --json title,body,headRefName,baseRefName,files,closingIssuesReferences,additions,deletions > /tmp/pr-review-${PR_NUMBER}-metadata.json gh pr diff $PR_NUMBERVerify branch target:
feature/*must targetdev, nevermain.Identify linked issues from metadata, PR body (
Closes #NNN), and title.Fetch each linked issue:
gh issue view <ISSUE> --json title,body,labels,commentsExtract acceptance criteria (AC-1, AC-2, ...) from issue checkboxes.
Checkout the PR branch:
gh pr checkout $PR_NUMBERInstall dependencies:
npm install 2>&1 | tail -20
Phase 2: Static Checks
Run each check and record pass/fail:
- TypeScript:
npm run typecheck - Lint:
npm run lint - Backend tests:
npm run test -w backend - Frontend tests:
npm run test -w frontend - Production build:
npm run build - Dependency audit:
npm audit --omit=dev --audit-level=high
Phase 3: Docker App Test
Skip if DOCKER_BLOCKED=true.
- Pre-clean:
$COMPOSE_CMD down -v --remove-orphans - Build:
$COMPOSE_CMD build - Start:
$COMPOSE_CMD up -d --wait --wait-timeout 180 backend frontend redis postgres-app timescaledb - Check migrations:
$COMPOSE_CMD logs backend | grep -iE 'migration|migrate' - API smoke tests:
- Login with dev defaults (
admin/changeme123) -- NEVER read .env or expose real credentials GET /health(unauthenticated)GET /api/dashboard,/api/containers,/api/settings(authenticated)- Frontend serves HTML at port 5273
- Login with dev defaults (
- Log inspection for errors
Phase 4: Playwright E2E Tests
Skip if Docker was skipped or failed.
npx playwright install chromium --with-deps
npx playwright test --project=auth --reporter=list --timeout=60000
npx playwright test --project=setup --project=chromium --reporter=list --timeout=60000
Always tear down Docker after: $COMPOSE_CMD down -v --remove-orphans
Phase 5: Code Review
For each changed file, read the full file (not just the diff) to understand context.
Analyze for:
- Logic bugs: off-by-one, race conditions, missing null checks
- Security (OWASP top 10): SQL injection, XSS, command injection, hardcoded secrets
- Project security rules:
- RBAC: every POST/PUT/DELETE needs
fastify.requireRole('admin') - Zod validation on all API boundaries
- Parameterized SQL only (no concatenation)
- LLM routes must use prompt injection guard
- Observer-first: no container-mutating actions without role + remediation approval
- RBAC: every POST/PUT/DELETE needs
- Error handling gaps: missing try/catch, swallowed errors
- Performance: N+1 queries, unbounded loops, memory leaks
- Debug artifacts:
console.log,debugger, commented-out code - Test quality: behavioral tests with real assertions
- Migration safety: flag
DROP COLUMN/TABLE, check defaults on NOT NULL - API contract breakage: search frontend for changed endpoint consumers
- New dependency review: duplication, size, maintenance, license
Severity levels:
- CRITICAL: Must fix (security, data loss, crash)
- WARNING: Should fix (logic bugs, missing validation)
- INFO: Suggestion only (style, minor optimization)
Phase 6: Story Validation
For each linked issue:
- Does the PR solve the problem described?
- Would the issue author be satisfied?
- Are there gaps between intent and implementation?
- Trace the user journey: Before -> After
Rate: FULLY ADDRESSED / PARTIALLY ADDRESSED / NOT ADDRESSED / EXCEEDS SCOPE
Phase 7: Post Review
Dismiss previous automated reviews:
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" --jq '.[] | select(.body | contains("Reviewed by PR Reviewer Agent")) | .id' | while read REVIEW_ID; do
gh api --method PUT "repos/${REPO}/pulls/${PR_NUMBER}/reviews/${REVIEW_ID}/dismissals" -f message="Superseded by updated review" 2>/dev/null || true
done
Determine verdict:
- APPROVE: All checks pass, no CRITICAL/WARNING findings, Docker passes, story FULLY ADDRESSED
- REQUEST_CHANGES: Any CRITICAL finding, story NOT ADDRESSED, tests/typecheck/build fail, wrong branch target
- COMMENT: Everything else (warnings, partial story, Docker skipped)
Write review to temp file /tmp/pr-review-<PR>-comment.md using this template:
## Automated PR Review
**PR**: #<NUMBER> -- <TITLE>
**Branch**: <HEAD> -> <BASE>
**Diff size**: <LINES> lines (+<ADD>/-<DEL>)
**Reviewed at**: <DATE>
---
### Static Checks
| Check | Status | Details |
|-------|--------|---------|
| TypeScript | PASS/FAIL | |
| Lint | PASS/FAIL | |
| Backend Tests | PASS/FAIL | |
| Frontend Tests | PASS/FAIL | |
| Production Build | PASS/FAIL | |
| npm audit | PASS/FAIL | |
| Branch Target | PASS/FAIL | |
### Docker App Test
| Check | Status |
|-------|--------|
| Containers Healthy | PASS/FAIL/SKIPPED |
| Login | PASS/FAIL/SKIPPED |
| Backend /health | PASS/FAIL/SKIPPED |
| API /dashboard | PASS/FAIL/SKIPPED |
| API /containers | PASS/FAIL/SKIPPED |
| Frontend loads | PASS/FAIL/SKIPPED |
| Migrations | PASS/FAIL/N/A |
| No error logs | PASS/FAIL/SKIPPED |
### Code Review Findings
| # | Severity | File:Line | Finding |
|---|----------|-----------|---------|
### Acceptance Criteria (Issue #<NUM>)
| AC | Criterion | Status | Evidence |
|----|-----------|--------|----------|
### Story Validation
**Issue intent**: <summary>
**Story completion**: <FULLY/PARTIALLY/NOT ADDRESSED>
**User journey**: Before: <problem> -> After: <solution>
**Gaps**: <list or None>
### Suggested Actions
1. **[REQUIRED/SHOULD FIX/OPTIONAL]** <description>
### Verdict: <APPROVE / REQUEST_CHANGES / COMMENT>
**Reason**: <justification>
---
Reviewed by PR Reviewer Agent | <DATE>
Post: gh pr review $PR_NUMBER --<approve|request-changes|comment> --body-file /tmp/pr-review-${PR_NUMBER}-comment.md
Phase 8: Auto-Fix Findings
Skip if APPROVE with no findings.
Fix in order: CI failures -> CRITICAL -> WARNING -> INFO
- Minimal surgical edits only
- Read full file context before editing
- Fix one finding at a time
- Track all fixes applied
Phase 9: Verify Fixes
Re-run static checks. Max 3 retry cycles. Re-run Docker tests if not blocked. Tear down Docker after.
Phase 10: Commit and Push
- Stage specific files only (never
git add -A) - Verify no secrets staged
- Commit:
git commit -m "fix: address review findings for PR #<NUMBER> - <fix descriptions> Co-Authored-By: Codex <noreply@openai.com>" - Push to the PR branch only. Never push to
mainordev.
Phase 11: Set Labels
Use GitHub REST API for label operations:
- All fixed + checks pass -> "Ready to Merge"
- Some issues remain -> "Reviewed by Agent"
Post a follow-up comment listing fixes applied.
Phase 12: Cleanup (ALWAYS runs)
- Tear down Docker:
$COMPOSE_CMD down -v --remove-orphans - Return to original branch:
git checkout "${ORIGINAL_BRANCH:-dev}" - Clean temp files
Rules
- Post review BEFORE making fixes
- Fix ALL severity levels (CRITICAL, WARNING, INFO, CI failures)
- Stage specific files only -- never
git add -A - Never push to
mainordev - Never commit secrets
- Use REST API for labels (
gh apinotgh pr edit --remove-label) - Max 3 retry cycles for fix verification
- Cleanup is mandatory regardless of which phase failed
- Volume isolation via compose overlay makes
down -vsafe - No credential exposure in review comments
- Docker failure is not fatal -- skip and use COMMENT verdict
- No linked issue = COMMENT verdict (except docs/CI/deps-only PRs)
- Evidence-based findings only with specific file:line citations
- Always read full file context, never review diffs in isolation
Input
Expects a PR number. Example: codex "review PR #769"
For multiple PRs, process sequentially with full cleanup between each.
Emergency Cleanup
/tmp/pr-review-<PR_NUMBER>-cleanup.sh
Source: kenhaesler/ai-portainer-dashboard — distributed by TomeVault.