| 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
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 [ -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 #. 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 # (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
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)
}'
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 for full framework-specific commands.
Summary:
- Auto-detect stack from changed file extensions (frontend:
.tsx/.jsx/.vue, backend: .py/.rb/.go/.rs/.php)
- Frontend: detect package manager → run
$PKG run lint / eslint → tsc --noEmit → prettier auto-fix
- Backend: Python (ruff → flake8 + mypy/pyright) | Ruby (rubocop) | Go (golangci-lint) | Rust (clippy + fmt) | PHP (phpstan + pint)
- Check for
make check-all / make lint / task lint convenience targets
- 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 |
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
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:
- Read the failed check logs:
gh run view <RUN_ID> --log-failed
- Diagnose the failure (lint errors, test failures, type errors, build errors)
- Fix the issues in code
- Run Phase 1.5 quality gate (lint + typecheck) to prevent pushing broken code again
- Commit with message:
fix: resolve CI failure — <brief description>
- Push
- STOP this tick — wait for CI re-run
-
If all checks green → proceed to Phase 3
Phase 3: Branch Freshness (Rebase onto Main)
git fetch origin main
BEHIND_COUNT=$(git rev-list --count HEAD..origin/main)
echo "Behind main by: $BEHIND_COUNT commits"
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 for full framework-specific commands.
3.5A: Service Readiness
- 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
- Port check: probe common ports (8000, 8080, 3000, 5000, 4000, 5432, 3306, 6379) via
nc / curl
- 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
- Apply: run the framework's migrate-forward command
- Round-trip verify (Alembic):
downgrade -1 → upgrade head to ensure reversibility
- Schema commit (Rails): auto-stage
db/schema.rb if changed after migration
- Client generation (Prisma):
npx prisma generate after deploy
- Database health check:
pg_isready (PostgreSQL) / mysqladmin ping (MySQL) via Docker
Phase 4: Browser Tests
Determine if the PR touches frontend or API-facing code:
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:
- Fix the failing tests or the code causing failures
- Run Phase 1.5 quality gate before pushing
- Commit and push
- 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):
- 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."
- Delete this cron job using CronDelete with the saved job ID (no further monitoring needed)
- STOP — do not merge
If DISABLE_AUTO_MERGE=false (default):
Execute the merge:
command -v gh && gh auth status
source "${RUNE_PLUGIN_ROOT}/scripts/lib/gh-account-resolver.sh" && rune_gh_ensure_correct_account
gh pr view <PR_NUMBER> --json state,mergeable --jq '{state, mergeable}'
GH_PROMPT_DISABLED=1 gh pr merge <PR_NUMBER> --squash --delete-branch
git checkout main && git pull origin main
After successful merge:
- Delete this cron job using CronDelete with the saved job ID
- 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.