PR Guardian
Continuously monitor open PRs across multiple repositories. For each new PR: review code, post inline comments, fix blocking issues, apply labels, and keep watching for new PRs on a recurring interval.
Make a todo list and track progress through all steps.
Step 0 — Determine targets and interval
Parse user input to extract:
- Repositories: GitHub org/repo URLs or names (e.g.
acme-corp/backend,https://github.com/org/repo) - Interval: How often to check (default:
1m). Accept formats like30s,1m,5m,10m. - Scope: Which PR states to monitor (default:
open)
If no repositories provided, ask the user.
If the user provides a GitHub organization URL, list all repos:
gh repo list <org> --json name,url --limit 50
Step 1 — Setup labels
Create standardized labels in all target repositories if they don't exist:
gh label create "reviewed" --repo <owner/repo> --color "1D76DB" --description "PR reviewed and validated" --force
gh label create "priority: critical" --repo <owner/repo> --color "B60205" --description "Runtime failures, data loss, security" --force
gh label create "priority: high" --repo <owner/repo> --color "D93F0B" --description "Incorrect behavior, performance, API breakage" --force
gh label create "priority: medium" --repo <owner/repo> --color "FBCA04" --description "Missing features, coverage gaps, bad UX" --force
gh label create "priority: low" --repo <owner/repo> --color "0E8A16" --description "Style, docs, minor improvements" --force
Step 2 — Scan for unreviewed PRs
For each target repository, list open PRs without the reviewed label:
gh pr list --repo <owner/repo> --state open --json number,title,labels \
--jq '.[] | select(.labels | map(.name) | index("reviewed") | not) | "#\(.number) \(.title)"'
If no unreviewed PRs found, report "No new PRs" and wait for next cycle.
Step 3 — Review each PR (parallel)
Launch one agent per unreviewed PR. Each agent performs:
3a. Collect context
gh pr view <number> --repo <owner/repo> --json title,body,state,isDraft,headRefName,baseRefName
gh pr diff <number> --repo <owner/repo>
Check for CLAUDE.md in the repo root and in modified directories.
3b. Analyze code changes
Scan the diff for:
Correctness & Logic
- Logic errors, wrong conditions, inverted booleans
- Off-by-one, boundary conditions
- Null/undefined/empty handling
- Async issues: race conditions, missing awaits, deadlocks
- Error handling: silent catches, swallowed exceptions, missing rollbacks
Security (OWASP Top 10)
- Injection (SQL, command, template)
- Broken authentication, sensitive data exposure
- Broken access control, IDOR, privilege escalation
- XSS, SSRF, path traversal, open redirects
- Hardcoded secrets or credentials
Performance
- N+1 queries, missing pagination
- Unnecessary recomputation, blocking I/O in async
- Missing caching, unbounded growth
Code Quality
- Unclear naming, functions too long
- DRY/KISS violations, dead code
- Missing error handling at boundaries
3c. Score each finding (0-100)
| Score | Meaning |
|---|---|
| 0 | False positive or pre-existing |
| 25 | Possible but uncertain |
| 50 | Real but minor |
| 75 | Highly likely, important |
| 100 | Confirmed, will occur frequently |
Discard findings below 75.
3d. Post inline review comments
Build JSON payload and post via GitHub API:
gh api repos/<owner>/<repo>/pulls/<number>/reviews --method POST --input /tmp/review_<number>.json
Build with Python to avoid shell escaping:
import json
review = {
"body": "...",
"event": "COMMENT",
"comments": [
{"path": "src/file.py", "position": 12, "body": "..."}
]
}
with open("/tmp/review_<number>.json", "w") as f:
json.dump(review, f)
Comment format:
[Blocking|Suggestion|Question|Comment]
**Problem** — description and why it matters.
**Failure scenario** — concrete example of when it breaks.
**Fix** — code suggestion with explanation.
3e. Update PR description
If incomplete, rebuild using:
## Description
<!-- Modified files with brief explanation -->
## Motivation and Context
<!-- Why + issue reference -->
## Types of changes
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation
## Checklist
- [ ] Self-review done
- [ ] Tests added
- [ ] Documentation updated
3f. Apply labels
- Apply
reviewedlabel - Apply contextual labels based on changes:
bug,enhancement,documentation,security - Only use labels that exist in the repo
Step 4 — Check for pending review comments
After reviewing new PRs, check ALL reviewed PRs for unresolved comments:
gh api repos/<owner>/<repo>/pulls/<number>/comments \
--jq '.[] | "\(.path):\(.original_line) \(.body | split("\n")[0])"'
Compare last comment date vs last commit date:
- If last commit < last comment: comments are unresolved
- If last commit > last comment: developer may have addressed them
Report unresolved comments with their severity.
Step 5 — Fix blocking comments (MANDATORY)
This step is NOT optional. After reviewing PRs, ALWAYS fix all [Blocking] comments automatically. Do NOT wait for the user to ask.
For each PR with unresolved [Blocking] comments:
Clone the repo on the PR branch:
gh repo clone <owner/repo> /tmp/fix-pr<number> -- -b <branch>Read the review comment to understand the required fix
Read the affected file(s)
Apply the fix
Commit with the appropriate convention:
git commit -m "$(cat <<'EOF' <emoji> <type> Fix review comment on <file> (#<issue>) EOF )"Push to the PR branch
Resolve the review thread using the GitHub GraphQL API:
# Get thread ID gh api graphql -f query='query { repository(owner:"<owner>",name:"<repo>") { pullRequest(number:<N>) { reviewThreads(first:20) { nodes { id isResolved comments(first:1) { nodes { body } } } } } } }' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id' # Resolve each thread gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"<THREAD_ID>"}) { thread { isResolved } } }'
Rules for fixing:
- ALWAYS fix
[Blocking]comments — this is mandatory, not optional - Also fix
[Suggestion]comments when the fix is straightforward (< 5 lines) - Never change code beyond what the comment requests
- Preserve existing code style and conventions
- If the fix requires architectural changes, report instead of fixing
- ALWAYS resolve the GitHub review thread after fixing — never leave threads unresolved
Step 6 — Verify all threads resolved
After fixing, verify NO unresolved threads remain:
gh api graphql -f query='query { repository(owner:"<owner>",name:"<repo>") { pullRequest(number:<N>) { reviewThreads(first:20) { nodes { isResolved } } } } }' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length'
If any remain unresolved, either fix them or explain why they cannot be fixed.
Step 7 — Merge ready PRs (MANDATORY)
After reviewing, fixing, and resolving threads, ALWAYS merge PRs that are ready. A PR is ready when:
- It has the
reviewedlabel - It has ZERO unresolved review threads
- It is not a draft
Merge sequentially (oldest first to minimize conflicts):
gh pr merge <number> --repo <owner/repo> --merge --delete-branch
If a merge fails due to conflicts:
- Clone the branch
- Fetch and merge origin/main
- Resolve conflicts — read files carefully, keep both changes
- Commit:
<emoji> Resolve merge conflicts with main (#<issue>) - Push and retry merge
After merging, close the corresponding GitHub issue if the PR body references it:
gh issue close <number> --repo <owner/repo> --reason completed --comment "Resolved by PR #XX"
Step 8 — Report results
After each cycle, output a summary:
## PR Monitor — Cycle Report
**Repos**: repo1, repo2, repo3
**New PRs reviewed**: N
**Blockers fixed**: N
**Threads resolved**: N
**PRs merged**: N
**Issues closed**: N
| Repo | PR | Title | Verdict | Issues | Fixed | Merged |
|------|----|-------|---------|--------|-------|--------|
| repo | #N | title | LGTM / N blockers | details | Y/N | Y/N |
**Next check**: in Xm
Step 9 — Start monitoring loop
Set up recurring check using CronCreate:
CronCreate:
cron: "*/N * * * *" (based on requested interval)
prompt: "Check for unreviewed PRs in <repos> and review them"
recurring: true
Report the job ID so the user can cancel with CronDelete <id>.
Guidelines
What to flag
- Real bugs introduced by the PR (not pre-existing)
- Security vulnerabilities
- Performance regressions
- Breaking API changes
- Missing error handling at boundaries
- CLAUDE.md violations
What NOT to flag
- Pre-existing issues not introduced by this PR
- Issues a linter/compiler would catch (assume CI runs those)
- Nitpicks a senior engineer wouldn't raise
- Intentional changes clearly part of the PR's goal
Review comment rules
- Use
[Blocking],[Suggestion],[Question],[Comment]prefixes - Be didactic — explain WHY something is wrong
- Use Nonviolent Communication
- Include concrete code fix suggestions
- Never mention AI, automation, or Claude in comments
- Link to code using full commit SHA
Parallel execution
- Review multiple PRs in parallel using Agent tool
- Fix multiple PRs in parallel when requested
- Never duplicate work across agents
Mandatory post-review actions
- ALWAYS fix [Blocking] comments — clone branch, apply fix, commit, push
- ALWAYS resolve GitHub review threads after fixing via GraphQL API
- ALWAYS verify zero unresolved threads remain before reporting
- ALWAYS merge ready PRs — reviewed + zero unresolved threads = merge
- ALWAYS close issues referenced by merged PRs
- ALWAYS resolve merge conflicts — clone, merge main, fix, push, retry
- Never leave a cycle with unresolved blocking comments
- Suggestions with < 5 line fixes should also be fixed automatically
Safety
- Never force-push or amend commits
- Never skip git hooks
- Stage specific files, never
git add . - Never commit secrets or credentials