Take every open PR end-to-end — diagnose failing checks, classify them (real defect vs mechanical vs environmental), fix the mechanical ones on the PR branch, re-verify, and stop at the merge gate. Use on demand ("handle the open PRs") or on an interval via /loop (e.g. "/loop 15m /pr-shepherd"). Merges only when the human has granted merge authority in the current session; otherwise reports ready-to-merge.
Install
npx skillscat add dshakes/compass/pr-shepherd Install via the SkillsCat registry.
pr-shepherd automatically diagnoses failing checks on open pull requests, classifies them as environmental, mechanical, or real defect, and fixes mechanical issues on the PR branch, re‑verifying before merge. It can be invoked on demand or on a schedule, and only merges when authorized, otherwise reports the PR as ready‑to‑merge or in need of human review.
PR shepherd — the loop's handoff + verification moves, applied to PRs
A loop has five moves: discovery → handoff → verification → persistence → scheduling
(see docs/20-loops.md). morning-triage is discovery — it finds
work. This skill is what happens after a PR exists: no PR sits red waiting for a human to
diagnose a check that a machine can read. It shepherds each open PR to exactly one of three
outcomes: merged (gate satisfied + authority granted), ready-to-merge (reported), or
inbox (needs a human, with the diagnosis already written).
Enumerate
gh pr list --state open --json number,title,author,isDraft,isCrossRepository,headRefName,statusCheckRollup
Skip drafts. Fail closed on forks: if isCrossRepository is true — or the field is
missing from the response — the PR is read-only: diagnose and comment, never check out into a
pushable worktree, never push.
Diagnose — read the check, don't guess
For each red check, read the actual failing step (gh run view <run-id> --log-failed) and
classify it. The class decides the action — this is the judgment step, don't skip it:
| class | signature | action |
|---|---|---|
| environmental | missing secrets (Dependabot actor), runner flake, quota | note it; if it recurs, fix the workflow (root cause), not the PR |
| mechanical | drift/sync gates, lint, format, lockfile, generated files | fix it on the PR branch |
| real defect | failing tests on changed logic, security finding | never auto-fix silently — comment the diagnosis on the PR, mark inbox |
A failure that pattern-matches "flake" still gets its log read once. Silence is not success:
a check that never ran (skipped, stuck queue) is a finding, not a pass.
Fix — mechanical only, verified before pushed
Work in a scratch worktree (git worktree add … origin/<branch>), never in the main checkout:
- Make the smallest fix the failing gate names (e.g. the gate says
run: cp sdlc/workflows/X .github/workflows/— run that). - Run the same gate locally and watch it pass before pushing. Never push a guess.
- Push to the PR branch with a commit message that names the gate it satisfies.
- Three strikes: after 3 failed fix attempts on one PR, stop — mark it
inboxwith what
was tried. A non-converging loop must stop spending
(the resource-ceiling principle).
Gate — where the loop stops
Merge requires all of:
- every non-environmental check green (environmental reds are named in the report, not ignored);
- the human granted merge authority in the current session ("validate and merge",
"/pr-shepherd --merge"). Authority is per-session, never assumed from a past one; - squash merge, never force-push, never merge red, never bypass a required check.
Without authority: report the PR as ready-to-merge with a one-line verdict per check.
The merge button is the human's; the shepherd's job is to make pressing it trivial.
Persist
Append one row per touched PR to state/shepherd.md (create if absent) so the next run
resumes instead of re-diagnosing. The agent forgets; the repo does not.
| pr | class | action | status |
|---|---|---|---|
| #71 | mechanical (mirror drift) | synced templates, pushed | merged |
| #74 | real defect (auth test) | diagnosis commented | inbox |Schedule
- On demand:
/pr-shepherd. - Interval (session open):
/loop 15m /pr-shepherd— each firing re-reads state, handles
what changed. - Unattended cadence (no session open):
compass schedule add pr-shepherd --twice-daily
— a bounded headless run (turn + budget + timeout capped, spend ledgered) at 09:07 and
17:07 while the laptop is awake, with merge authority and the same rails
(sdlc/routines/prompts/pr-shepherd.md). Crontab skips missed slots; it doesn't catch up. - Every firing re-reads
state/shepherd.mdfirst; a PR alreadyinboxstays with the human
until they act — don't re-litigate it every run.