Automated PR guardian loop — schedules a recurring cron (every 5 min) that checks review comments, CI/CD status, branch freshness, runs browser tests, and auto-merges when everything is green. Use when you want hands-off PR shepherding to merge. Triggers on "pr guardian", "auto merge loop", "watch my PR", "shepherd PR", "auto merge", "monitor PR", "drive PR to merge".
Scanned 5/28/2026
Install to Claude Code
npx -y skills add vinhnxv/rune --skill pr-guardian --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Pr Guardian?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/vinhnxv-pr-guardian)More formats (shields.io, HTML) on the badges page.
---
name: pr-guardian
description: |
Automated PR guardian loop — schedules a recurring cron (every 5 min) that checks
review comments, CI/CD status, branch freshness, runs browser tests, and auto-merges
when everything is green. Use when you want hands-off PR shepherding to merge.
Triggers on "pr guardian", "auto merge loop", "watch my PR", "shepherd PR",
"auto merge", "monitor PR", "drive PR to merge".
user-invocable: true
disable-model-invocation: false
---
# PR Guardian — Automated PR Shepherd Loop
An autonomous cron-based loop that watches your current PR and drives it to merge.
## What It Does (every 5 minutes)
```
┌─────────────────────────────────────────────────────────────┐
│ PR Guardian Loop │
├─────────────────────────────────────────────────────────────┤
│ 1. Check PR review comments │
│ └─ If unresolved → /rune:resolve-all-gh-pr-comments │
│ 1.5 Local quality gate (lint + typecheck) │
│ └─ Auto-detect stack → run lint/format/typecheck │
│ └─ Runs BEFORE every git push │
│ 2. Check CI/CD status │
│ └─ If red → diagnose & fix → lint gate → push │
│ 3. Check branch freshness vs main │
│ └─ If behind → rebase → lint gate → push │
│ 3.5 Local services & migration health │
│ └─ A: Start Docker Compose / verify dev servers │
│ └─ B: Detect & resolve migration conflicts │
│ (Alembic, Django, Rails, Prisma, Sequelize, etc.) │
│ └─ C: Apply pending migrations + round-trip verify │
│ 4. Run browser tests (if applicable) │
│ └─ Frontend/API changes → test related routes │
│ 5. All green + no comments? │
│ └─ Merge PR (squash + delete branch) │
│ └─ Delete this cron job │
└─────────────────────────────────────────────────────────────┘
```
## Usage
```
/rune:pr-guardian # Start the guardian on current branch's PR
/rune:pr-guardian <PR_NUMBER> # Watch a specific PR
/rune:pr-guardian --disable-auto-merge # Watch PR but skip auto-merge (monitor + fix only)
/rune:pr-guardian <PR_NUMBER> --disable-auto-merge # Watch specific PR, skip auto-merge
```
## Activation
When this skill is invoked, perform these steps **in order**:
### Step 0 — Parse Arguments & Detect PR
```bash
# Parse arguments — extract PR number and flags
PR_NUMBER=""
DISABLE_AUTO_MERGE=false
for arg in $ARGUMENTS; do
case "$arg" in
--disable-auto-merge) DISABLE_AUTO_MERGE=true ;;
[0-9]*) PR_NUMBER="$arg" ;;
esac
done
# If no PR number provided, detect from current branch
if [ -z "$PR_NUMBER" ]; then
gh pr view --json number -q '.number'
fi
```
If no open PR is found on the current branch, **STOP** and tell the user:
> "No open PR found on this branch. Push your branch and create a PR first."
### Step 1 — Schedule the Cron
Use **CronCreate** with:
- **cron**: `*/5 * * * *` (every 5 minutes)
- **recurring**: `true`
- **prompt**: The full guardian loop prompt below
**Save the returned job ID** — you will need it for self-deletion in Step 5.
Tell the user:
- If `DISABLE_AUTO_MERGE=false`:
> "PR Guardian activated for PR #<NUMBER>. Checking every 5 minutes. I'll auto-merge when everything is green. The cron auto-expires after 7 days if not completed sooner."
- If `DISABLE_AUTO_MERGE=true`:
> "PR Guardian activated for PR #<NUMBER> (monitor-only mode). Checking every 5 minutes. Auto-merge is disabled — I'll fix issues but won't merge. The cron auto-expires after 7 days."
### Step 2 — The Guardian Loop Prompt
Each cron tick should execute this prompt (adapt the PR number):
---
**You are the PR Guardian. Execute these steps for PR #<PR_NUMBER> in order. Stop at the first step that requires action and re-check on the next tick. Auto-merge is <ENABLED|DISABLED based on DISABLE_AUTO_MERGE flag>.**
#### Phase 1: Review Comments
```bash
gh pr view <PR_NUMBER> --json reviewDecision,reviews,comments --jq '{
decision: .reviewDecision,
pending_reviews: [.reviews[] | select(.state == "CHANGES_REQUESTED" or .state == "COMMENTED") | {author: .author.login, state: .state}],
comment_count: (.comments | length)
}'
```
- If there are **unresolved review comments** or **CHANGES_REQUESTED** reviews:
1. Invoke `/rune:resolve-all-gh-pr-comments` skill to resolve all comments
2. **Run local quality gate** (Phase 1.5) before pushing
3. Push fixes
4. **STOP this tick** — wait for next cycle to re-check
- If no actionable comments → proceed to Phase 1.5 (if code changed since last tick) or Phase 2
#### Phase 1.5: Local Quality Gate (Lint & Typecheck)
Before every `git push`, run the project's lint and typecheck commands to catch errors
locally instead of waiting for a full CI cycle.
See [local-quality-gate.md](references/local-quality-gate.md) for full framework-specific commands.
**Summary**:
1. Auto-detect stack from changed file extensions (frontend: `.tsx/.jsx/.vue`, backend: `.py/.rb/.go/.rs/.php`)
2. **Frontend**: detect package manager → run `$PKG run lint` / eslint → `tsc --noEmit` → prettier auto-fix
3. **Backend**: Python (ruff → flake8 + mypy/pyright) | Ruby (rubocop) | Go (golangci-lint) | Rust (clippy + fmt) | PHP (phpstan + pint)
4. Check for `make check-all` / `make lint` / `task lint` convenience targets
5. Stage auto-fixed files → commit as `style: auto-fix lint and formatting`
| Stack | Lint | Typecheck | Format |
|-------|------|-----------|--------|
| JS/TS | eslint / `$PKG run lint` | `tsc --noEmit` | prettier |
| Python | ruff → flake8 | mypy / pyright | ruff format |
| Ruby | rubocop | — | rubocop |
| Go | golangci-lint → go vet | — | gofmt |
| Rust | cargo clippy | built-in | cargo fmt |
| PHP | phpstan | phpstan | pint / php-cs-fixer |
- If lint/typecheck **fails** with unfixable errors:
1. Fix the errors in code
2. Re-run the quality gate
3. If still failing after 2 attempts → **STOP this tick** and commit what works
- If lint/typecheck **passes** → proceed to push (or Phase 2 if no push needed)
**Key rule**: This phase runs BEFORE every `git push` in the guardian loop — that includes
after Phase 1 (comment fixes), Phase 2 (CI fixes), Phase 3 (rebase), and Phase 4 (browser test fixes).
#### Phase 2: CI/CD Status
```bash
gh pr checks <PR_NUMBER> --json name,state,conclusion --jq '.[] | select(.state != "COMPLETED" or .conclusion != "SUCCESS")'
```
- If any checks are **pending**: **STOP this tick** — wait for CI to finish
- If any checks are **failed/red**:
1. Read the failed check logs: `gh run view <RUN_ID> --log-failed`
2. Diagnose the failure (lint errors, test failures, type errors, build errors)
3. Fix the issues in code
4. **Run Phase 1.5 quality gate** (lint + typecheck) to prevent pushing broken code again
5. Commit with message: `fix: resolve CI failure — <brief description>`
6. Push
7. **STOP this tick** — wait for CI re-run
- If all checks **green** → proceed to Phase 3
#### Phase 3: Branch Freshness (Rebase onto Main)
```bash
git fetch origin main
BEHIND_COUNT=$(git rev-list --count HEAD..origin/main)
echo "Behind main by: $BEHIND_COUNT commits"
```
- If `BEHIND_COUNT > 0`:
1. `git rebase origin/main`
2. If conflicts arise:
- Resolve code conflicts intelligently (read both sides, pick correct resolution)
- **Migration conflicts**: Detect the project's migration framework and resolve:
- Alembic: `alembic heads` → if multiple, `alembic merge heads`
- Rails: check `db/migrate/` for conflicting timestamps
- Prisma: `npx prisma migrate resolve`
- Other: resolve file conflicts manually
- `git rebase --continue` after resolving
3. **Run Phase 1.5 quality gate** (lint + typecheck) on rebased code
4. `git push --force-with-lease` (safe force push after rebase)
5. **STOP this tick** — wait for CI to re-run on rebased code
- If already up-to-date → proceed to Phase 3.5
#### Phase 3.5: Local Services & Migration Health
Before running browser tests, ensure local services are up and the database schema is current.
See [services-and-migrations.md](references/services-and-migrations.md) for full framework-specific commands.
##### 3.5A: Service Readiness
1. **Docker Compose**: detect `docker-compose.yml` / `compose.yml` → `docker compose up -d --wait` → wait for health checks (max 60s) → rebuild if `Dockerfile` / dependency lockfiles changed
2. **Port check**: probe common ports (8000, 8080, 3000, 5000, 4000, 5432, 3306, 6379) via `nc` / `curl`
3. **Warning**: if no running server and no Docker Compose → warn that browser tests may fail
##### 3.5B: Migration Conflict Detection & Resolution
Auto-detect migration framework and resolve conflicts **before** applying:
| Framework | Conflict Detection | Auto-Resolution |
|-----------|-------------------|-----------------|
| **Alembic** | `alembic heads` → multiple heads? | `alembic merge heads` + commit |
| **Django** | `showmigrations --plan` → CONFLICT? | `makemigrations --merge` per app + commit |
| **Rails** | Duplicate timestamps in `db/migrate/` | Flag for manual resolution |
| **Prisma** | `prisma migrate status` + drift check | Flag drift for review |
| **Sequelize** | `db:migrate:status` | — |
| **Knex** | `migrate:status` | — |
| **TypeORM** | `migration:show` | — |
Command resolution (Python): `uv run alembic` → `.venv/bin/alembic` → `alembic` (same pattern for Django `manage.py`).
##### 3.5C: Apply Migrations & Verify Database
1. **Apply**: run the framework's migrate-forward command
2. **Round-trip verify** (Alembic): `downgrade -1` → `upgrade head` to ensure reversibility
3. **Schema commit** (Rails): auto-stage `db/schema.rb` if changed after migration
4. **Client generation** (Prisma): `npx prisma generate` after deploy
5. **Database health check**: `pg_isready` (PostgreSQL) / `mysqladmin ping` (MySQL) via Docker
- If migrations **fail** or conflicts cannot be resolved:
1. Attempt to fix the migration code
2. Re-run conflict detection and apply
3. If still failing after 2 attempts → **STOP this tick** and ask user for guidance
- If migrations **pass** → proceed to Phase 4
#### Phase 4: Browser Tests
Determine if the PR touches frontend or API-facing code:
```bash
# Get changed files
gh pr diff <PR_NUMBER> --name-only
```
- If **frontend files changed** (components, pages, views, styles):
- Run targeted browser tests on changed routes (use the testing skill's resolveTestScope() to pick routes — the standalone `/rune:test-browser` skill was removed in v3.0.0-alpha.2; trigger `/rune:arc` Phase 7.7 or invoke the `e2e-browser-tester` agent directly if a fresh harness is available)
- If **backend/API-only files changed**:
- Identify related frontend pages that consume the changed APIs
- Run targeted browser tests on those related frontend flows
- If **docs/config/infra only**: skip browser tests
If browser tests **fail**:
1. Fix the failing tests or the code causing failures
2. **Run Phase 1.5 quality gate** before pushing
3. Commit and push
4. **STOP this tick**
If browser tests **pass** → proceed to Phase 5
#### Phase 5: Merge & Cleanup
All conditions met:
- No unresolved review comments
- Local lint + typecheck passes
- All CI/CD checks green
- Branch is up-to-date with main
- Migrations applied, no conflicts, database healthy
- Browser tests pass (or skipped)
**If `DISABLE_AUTO_MERGE=true`** (monitor-only mode):
1. Tell the user:
> "PR #<PR_NUMBER> is fully green — all checks pass, no unresolved comments, branch is up-to-date. Auto-merge is disabled. Merge manually when ready."
2. **Delete this cron job** using CronDelete with the saved job ID (no further monitoring needed)
3. **STOP** — do not merge
**If `DISABLE_AUTO_MERGE=false`** (default):
Execute the merge:
```bash
# 1. Verify gh CLI
command -v gh && gh auth status
# 2. Switch to correct GitHub account
source "${RUNE_PLUGIN_ROOT}/scripts/lib/gh-account-resolver.sh" && rune_gh_ensure_correct_account
# 3. Verify PR is still open and mergeable
gh pr view <PR_NUMBER> --json state,mergeable --jq '{state, mergeable}'
# 4. Squash merge + delete branch
GH_PROMPT_DISABLED=1 gh pr merge <PR_NUMBER> --squash --delete-branch
# 5. Sync local
git checkout main && git pull origin main
```
After successful merge:
1. **Delete this cron job** using CronDelete with the saved job ID
2. Tell the user:
> "PR #<PR_NUMBER> has been merged successfully! Guardian loop terminated."
---
## Safety Guardrails
- **Force push**: Only `--force-with-lease` after rebase (never `--force`)
- **Merge conflicts**: Always resolve intelligently, never blindly accept one side
- **Migration conflicts**: Detect framework automatically — verify round-trip after resolution
- **Migration round-trip**: Always verify downgrade+upgrade works (Alembic). Check schema.rb is updated (Rails)
- **Docker services**: Never `docker compose down` — only `up -d`. Guardian should add services, not destroy them
- **Database safety**: Never run destructive database commands (`DROP`, `TRUNCATE`). Only `migrate` forward
- **Max retries**: If the same phase fails 3 consecutive ticks, **pause and ask the user** for guidance instead of looping forever
- **7-day auto-expiry**: CronCreate recurring jobs auto-expire after 7 days — the guardian will not run forever
- **GitHub account**: Always uses `gh-account-resolver.sh` to ensure correct account before merge
## Cancellation
To stop the guardian manually, the user can say:
> "Stop the PR guardian" / "Cancel the loop"
This should trigger `CronDelete` with the saved job ID.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!