Autonomous PR CI monitor and review-comment resolver for Claude Code.
Concrete improvements to an agentic PR-review workflow:
- Faster monitor loops — one batched GraphQL query per tick (see docs/graphql.md) instead of N REST round-trips
- Lower context usage per iteration — classification lives in TypeScript; the agent receives one decision per tick and never sees raw GraphQL payloads or resolved threads
- Deterministic output —
--format=jsonand--format=textsurface equivalent information, so both scripts and agents see the same state - Prompt-cache friendly — the 4-minute default tick is tuned to Claude's 5-minute prompt-cache TTL (tunable via
watch.interval) - Reduced GitHub rate-limit exposure — read results share a 5-minute file cache with atomic writes (see docs/cache.md)
- No MCP surface — skills call the CLI via
npx; no long-lived MCP server, no extra auth boundary, smaller reasoning surface - Skills over subagents — skill prompts inject into the main conversation rather than spawning a subagent that reloads CLAUDE.md every turn
- Safe to interrupt — all state lives in the PR on GitHub; the cron loop self-terminates when the PR is merged, closed, or settles after ready-delay
- Reduced agent context — logic lives in the CLI, not the prompt
- Reduced GitHub rate-limit exhaustion — primary PR state is fetched via a batched GraphQL query
- Fewer tool calls — comment resolutions are batched; resolved threads never reach the agent
- No MCP — smaller reasoning surface, much faster than the GitHub MCP
- No vendor lock-in — runs against
gh+git; no hosted service required - Skills over subagents — subagents reload all CLAUDE.md context on every turn; skills inject into the main conversation instead, keeping cost low
- JSON/text parity —
--format=jsonand--format=textcarry equivalent information; every field in one has a representation in the other
pr-shepherd is split into a CLI (deterministic, pure GitHub I/O) and three Claude Code skills that wrap the CLI with model-driven flow and mutation.
Deterministic commands that fetch, classify, and mutate PR state without invoking the model:
check— one-shot PR snapshot (merge state, CI results, unresolved comments) via a single batched GraphQL queryresolve— fetch/triage mode auto-resolves outdated threads and returns actionable items (each annotated with any parseable```suggestionblock); mutate mode batch-resolves threads, minimizes comments, and dismisses reviews by ID, polling--require-shaso reviewers see the push before threads closecommit-suggestions— agent-side equivalent of GitHub's "Commit suggestion" / "Add suggestion to batch" buttons: parses reviewer```suggestionblocks, creates a single remote commit viacreateCommitOnBranch(co-crediting each reviewer), and resolves the applied threadsiterate— classifies current PR state and emits exactly one of eight actions:cooldown,wait,rerun_ci,mark_ready,rebase,fix_code,cancel,escalate(see docs/actions.md)status— multi-PR summary table, one lightweight GraphQL query per PR in parallel
Cross-cutting machinery: file cache with atomic writes (docs/cache.md), merge-status derivation (docs/merge-status.md), CI failure classification into actionable / infrastructure / timeout / flaky (docs/checks.md), outdated-thread detection (docs/comments.md), deprecation-warning-aware RC loader.
Claude Code skills that wrap the CLI with model-driven triage, code edits, and flow control:
/pr-shepherd:check— callscheck --format=jsonand prints a human summary; never declares "ready to merge" unless every gate passes (merge status CLEAN, status READY, Copilot review not in progress)/pr-shepherd:monitor— creates a/loopcron job (4-minute default, 8-hour expiry, 50-turn cap), deduplicates via a# pr-shepherd-loop:pr=<N>tag inCronList, dispatches on the[ACTION]H1 tag each tick, runs rebase scripts and fix instructions in the main conversation/pr-shepherd:resolve— runsresolve --fetch, triages every returned item into Fixed / Actionable / Not-relevant / Outdated / Acknowledge (no silent drops — this is a project invariant), preferscommit-suggestionsfor threads carrying a```suggestionblock so the reviewer's change is applied verbatim and co-credited, applies any remaining edits by hand, cancels stale CI runs, pushes, then callsresolvein mutate mode with--require-sha
See docs/skills.md for full skill reference.
Note: Skill and plugin install methods add the skill definitions only — they do not install the
pr-shepherdCLI. The skills invokenpx pr-shepherd, so you also need the CLI available. If you're usingpr-shepherdas development tooling for your repo, install it as a dev dependency sonpxresolves it without prompting:npm install --save-dev pr-shepherdA plain
npm install pr-shepherdadds it to regular dependencies instead; use that only if you specifically want it underdependencies. Or install globally:npm install -g pr-shepherd.
npx skills add jonathanong/pr-shepherdInstalls the three skills (check, monitor, resolve) into your agent's skill directory (.claude/skills/ for project scope, ~/.claude/skills/ with -g for global scope). Powered by skills.sh.
claude /plugin marketplace add jonathanong/pr-shepherd
claude /plugin install pr-shepherdThis repo ships two marketplace.json files that serve different install flows: the root marketplace.json resolves the plugin from the npm registry (used by the claude /plugin marketplace add command above); .claude-plugin/marketplace.json is the owner-level registry manifest that resolves the plugin from the local plugin directory (used when Claude Code installs from a local or git-based source). Both files are needed to support these two install paths.
See Usage below.
If you don't want the full plugin, create a project-local (or user-scope)
slash command that wraps the CLI directly. This still requires pr-shepherd
to be installed in the repository first (npm install pr-shepherd), so that
npx pr-shepherd ... runs without prompting to install the package.
-
Create the command file:
- Project-scope:
.claude/commands/pr-check.md - User-scope:
~/.claude/commands/pr-check.md
- Project-scope:
-
Paste this as the file contents:
--- description: "Check GitHub CI status and review comments for the current PR" argument-hint: "[PR number or URL ...]" allowed-tools: ["Bash", "Read", "Grep"] --- # PR Status Check ## Arguments: $ARGUMENTS ## Resolve PR number(s) 1. If `$ARGUMENTS` contains PR numbers or GitHub PR URLs, extract the number(s). 2. Otherwise, infer: `gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --json number --jq '.[0].number'` 3. If no PR found, report an error and stop. ## Run the check ```bash npx pr-shepherd check <PR_NUMBER> --format=json ``` Parse the JSON and report: - **Merge status** (`report.mergeStatus.status`): CLEAN | BEHIND | CONFLICTS | BLOCKED | UNSTABLE | DRAFT | UNKNOWN - **CI check results** (`report.checks`): passing count, failing names, in-progress names - **Unresolved review comments** (`report.threads.actionable` + `report.comments.actionable`): count + details
-
Use it in Claude Code:
/pr-check /pr-check 42
For monitor and resolve custom commands, do not copy the
plugin/skills/ files directly — those contain skill/plugin-specific
frontmatter that is not valid for .claude/commands/ files. Instead, create
.claude/commands/pr-monitor.md and/or .claude/commands/pr-resolve.md
using the same command-file structure as the pr-check example above, with
the CLI invocation changed to npx pr-shepherd iterate ... or
npx pr-shepherd resolve .... To drive the CLI without Claude at all, see
docs/usage.md.
npm install -g pr-shepherdCreates a cron loop that fires every 4 minutes, checks CI and review comments, fixes issues, and marks the PR ready for review when clean. The loop cancels automatically when the PR is merged or closed.
/pr-shepherd:monitor # infer PR from current branch
/pr-shepherd:monitor 42
/pr-shepherd:monitor 42 every 8m
/pr-shepherd:monitor 42 --ready-delay 15m
One-shot status snapshot — merge state, CI results, and unresolved comments. Accepts multiple PR numbers.
/pr-shepherd:check # infer from branch
/pr-shepherd:check 42
/pr-shepherd:check 41 42 43
Fetches all actionable threads and comments, triages them, applies fixes,
pushes, then resolves/minimizes/dismisses via --require-sha (waits until
GitHub has seen the push before resolving).
/pr-shepherd:resolve # infer from branch
/pr-shepherd:resolve 42
See docs/skills.md for full argument reference.
On each tick (4-minute default, tunable via watch.interval): fetch PR state in one GraphQL batch → classify CI, comments, and merge status → take one action (fix code, rebase, rerun CI, mark ready, or wait). See docs/flow.md for the full decision tree.
pr-shepherd -v|--version # print installed version
pr-shepherd check [PR] # read-only PR status snapshot
pr-shepherd resolve [PR] [--fetch | --resolve-thread-ids …]
pr-shepherd commit-suggestions [PR] --thread-ids A,B # apply reviewer ```suggestion blocks as one commit
pr-shepherd iterate [PR] [--cooldown-seconds N] [--ready-delay Nm] [--last-push-time N] [--no-commit-suggestions]
pr-shepherd status PR1 [PR2 …] # multi-PR tableCommon flags (all subcommands):
| Flag | Default | Description |
|---|---|---|
--format text|json |
text |
Output format |
--no-cache |
false | Bypass the 5-minute file cache |
--cache-ttl N |
300 |
Cache TTL in seconds; takes precedence over PR_SHEPHERD_CACHE_TTL_SECONDS env var |
Read-only PR status snapshot. Fetches CI results, merge state, and review comments in one GraphQL batch. PR number is inferred from the current branch when omitted.
pr-shepherd check # infer PR from current branch
pr-shepherd check 42
pr-shepherd check 42 --format=json
pr-shepherd check 42 --no-cacheExit codes: 0 READY · 2 IN_PROGRESS · 3 UNRESOLVED_COMMENTS · 1 all other statuses
Example output:
PR #42 — owner/repo
Status: UNRESOLVED_COMMENTS
Merge Status: CLEAN
mergeStateStatus: CLEAN
mergeable: MERGEABLE
reviewDecision: APPROVED
isDraft: false
copilotReviewInProgress:false
CI Checks: 3/3 passed
Actionable Review Threads (1):
- threadId=RT_kwDOBxyz123 src/api.ts:47 (@reviewer)
Please add error handling here
Summary: 1 actionable item(s) remaining
Two modes: fetch (default) auto-resolves outdated threads and returns actionable items; mutate resolves/minimizes/dismisses specific IDs after you push fixes.
Fetch mode:
pr-shepherd resolve # fetch + auto-resolve outdated threads
pr-shepherd resolve 42 --fetch --format=jsonActionable Review Threads (2):
- threadId=RT_kwDOabc src/api.ts:47 (@reviewer): Please add error handling here
- threadId=RT_kwDOdef src/utils.ts:12 (@bot): Consider using a const here
Summary: 2 actionable item(s)
Mutate mode (after pushing fixes):
pr-shepherd resolve 42 \
--resolve-thread-ids RT_kwDOabc,RT_kwDOdef \
--minimize-comment-ids IC_kwDOxyz \
--dismiss-review-ids PRR_kwDO123 \
--message "Switched query to parameterized form in src/db.ts" \
--require-sha $(git rev-parse HEAD)Resolved threads (2): RT_kwDOabc, RT_kwDOdef
Minimized comments (1): IC_kwDOxyz
Dismissed reviews (1): PRR_kwDO123
--require-sha polls GitHub until the PR head matches the SHA before mutating — ensures reviewers see the fix before threads are closed. Exit code: always 0. --message is required only when --dismiss-review-ids is set, and should describe the specific fix — it is shown to the reviewer on GitHub.
Agent-side equivalent of GitHub's "Commit suggestion" and "Add suggestion to batch" buttons. Parses the ```suggestion block on each specified thread, applies each suggestion to its target file, and creates a single remote commit via the createCommitOnBranch GraphQL mutation — co-crediting every distinct reviewer with a Co-authored-by trailer. The threads it applies are resolved in the same run.
pr-shepherd commit-suggestions 42 --thread-ids PRRT_abc,PRRT_def --format=jsonRequires a clean working tree (errors out if git status --porcelain is non-empty). After a successful run, your local checkout is one commit behind remote — the output's postActionInstruction tells the agent to git pull --ff-only before editing anything else. Threads whose body lacks a parseable suggestion, that are already resolved or outdated, or whose line range overlaps another applied suggestion on the same file are reported as skipped so callers can fall back to a manual fix.
Gated by actions.commitSuggestions (default true) — /pr-shepherd:resolve invokes this automatically for threads that resolve --fetch annotates with a suggestion field.
Example JSON output:
{
"pr": 42,
"repo": "owner/repo",
"newHeadSha": "207bb9d…",
"commitUrl": "https://github.com/owner/repo/commit/207bb9d…",
"threads": [
{ "id": "PRRT_abc", "status": "applied", "path": "src/foo.ts", "author": "alice" },
{ "id": "PRRT_def", "status": "skipped", "reason": "no suggestion block in comment body" }
],
"applied": true,
"postActionInstruction": "Your local checkout is now one commit behind remote. Run `git pull --ff-only` before making any further edits."
}Exit codes: 0 at least one suggestion applied · 1 every requested thread skipped.
One monitor tick: classifies current PR state and emits a single action. Used by the cron loop; the monitor skill calls this every 4 minutes and acts on the result. See docs/iterate-flow.md for the full decision tree.
pr-shepherd iterate 42 --no-cache --format=json \
--ready-delay 10m \
--last-push-time "$(git log -1 --format=%ct HEAD)"Flags:
| Flag | Default | Description |
|---|---|---|
--ready-delay Nm |
10m |
Settle window before the loop cancels after READY |
--cooldown-seconds N |
30 |
Wait after a push before reading CI |
--last-push-time N |
— | Unix timestamp hint embedded in the result |
--no-auto-rerun |
false | Return wait instead of rerunning transient CI |
--no-auto-mark-ready |
false | Skip converting draft → ready-for-review |
--no-auto-cancel-actionable |
false | Skip cancelling actionable failing runs |
--no-commit-suggestions |
false | Suppress the all-suggestions fix_code shortcut |
Markdown output (default). The monitor SKILL reads the [ACTION] tag from the H1 heading to decide what to do. Every action emits an H1, a bolded base-fields line, a bolded summary line, then an action-specific body. Example for [WAIT]:
# PR #42 [WAIT]
**status** `READY` · **merge** `CLEAN` · **state** `OPEN` · **repo** `owner/repo`
**summary** 3 passing, 0 skipped, 0 filtered, 0 inProgress · **remainingSeconds** 540 · **copilotReviewInProgress** false · **isDraft** false · **shouldCancel** false
WAIT: 3 passing, 0 in-progress — 540s until auto-cancelSee docs/actions.md for the other seven actions — cooldown, rerun_ci, mark_ready, cancel, rebase, fix_code, escalate. fix_code is the richest: it emits sections for ## Review threads, ## Actionable comments, ## Failing checks, ## Changes-requested reviews, ## Noise (minimize only), ## Cancelled runs, ## Post-fix push, and ## Instructions. When every actionable thread carries a parseable ```suggestion block, fix_code short-circuits to a ## Commit suggestions bundle (commit-suggestions: + git pull --ff-only) instead of the rebase-and-push ceremony — pass --no-commit-suggestions to disable.
Both --format=text (default Markdown) and --format=json carry equivalent information — every field exposed in JSON has a corresponding Markdown representation, and vice versa.
JSON output (--format=json, compact single line):
{
"pr": 42,
"repo": "owner/repo",
"status": "READY",
"state": "OPEN",
"mergeStateStatus": "CLEAN",
"copilotReviewInProgress": false,
"isDraft": false,
"shouldCancel": false,
"remainingSeconds": 540,
"summary": { "passing": 3, "skipped": 0, "filtered": 0, "inProgress": 0 },
"action": "wait"
}Exit codes: 0 wait/cooldown/rerun_ci/mark_ready · 1 fix_code/rebase · 2 cancel · 3 escalate
Multi-PR summary table. One lightweight GraphQL query per PR, run in parallel.
pr-shepherd status 41 42 43
pr-shepherd status 100 --format=json
# owner/repo — PR status (3)
PR #41 Add new feature for user authentication READY SUCCESS
PR #42 Refactor internal module IN PROGRESS PENDING
PR #43 Fix edge case in parser BLOCKED SUCCESS (threads truncated — run shepherd check for full count)
Exit code: 0 if every PR is READY, 1 otherwise.
Create a .pr-shepherdrc.yml in your project root (or any parent directory) to override defaults. The loader walks up from cwd to $HOME (if $HOME is on that path) or the filesystem root; the first match wins.
iterate:
cooldownSeconds: 60 # wait longer after a push before reading CI
checks:
ciTriggerEvents:
- pull_request
- pull_request_target
- merge_group # add for merge-queue repos
actions:
autoRebase: false # disable for repos that enforce merge commitsAll supported keys:
| Key | Default | Purpose |
|---|---|---|
cache.ttlSeconds |
300 |
File-cache TTL for read operations |
iterate.cooldownSeconds |
30 |
Wait after a push before reading CI |
iterate.fixAttemptsPerThread |
3 |
Max fix attempts per unresolved thread before escalate |
watch.interval |
"4m" |
Monitor tick interval (tuned to Claude's 5-min prompt-cache TTL) |
watch.readyDelayMinutes |
10 |
Settle window after READY before the monitor loop cancels |
watch.expiresHours |
8 |
Max lifetime of a monitor cron job |
watch.maxTurns |
50 |
Max monitor ticks per session |
resolve.concurrency |
4 |
Parallel fanout for per-thread GraphQL fetches |
resolve.shaPoll.intervalMs |
2000 |
Poll interval when waiting for --require-sha to land on GitHub |
resolve.shaPoll.maxAttempts |
10 |
Max --require-sha polls before giving up |
resolve.fetchReviewSummaries |
true |
Surface COMMENTED review summaries in resolve --fetch output |
checks.ciTriggerEvents |
["pull_request", "pull_request_target"] |
Workflow on: events treated as PR CI (add merge_group for merge-queue repos) |
checks.timeoutPatterns |
see src/config.json |
Log patterns that classify a failure as timeout |
checks.infraPatterns |
see src/config.json |
Log patterns that classify a failure as infrastructure |
checks.logMaxLines |
50 |
Max log lines kept per failing check |
checks.logMaxChars |
3000 |
Max log characters kept per failing check |
mergeStatus.blockingReviewerLogins |
["copilot"] |
Reviewer logins whose pending review blocks mark_ready |
actions.autoResolveOutdated |
true |
Auto-resolve threads that point to code no longer in the PR diff |
actions.autoRebase |
true |
Emit rebase for flaky failures when the branch is behind base |
actions.autoMarkReady |
true |
Emit mark_ready when a draft PR's CI goes clean |
actions.commitSuggestions |
true |
Route /pr-shepherd:resolve through commit-suggestions for threads with a ```suggestion block |
Environment variables: GH_TOKEN / GITHUB_TOKEN (auth; falls back to gh auth token), PR_SHEPHERD_CACHE_DIR (override cache base dir), PR_SHEPHERD_CACHE_TTL_SECONDS (override cache TTL; --cache-ttl takes precedence over this env var, which in turn takes precedence over the RC/config value).
See docs/configuration.md for full semantics and deprecated-key migration.
- Node.js ≥ 22.0.0
- A GitHub token: set
GH_TOKENorGITHUB_TOKEN, or install and authenticate theghCLI (gh auth login) — pr-shepherd usesgh auth tokenas a fallback. Thereposcope is required for private repositories. git
Full reference: docs/README.md — CLI usage, skills, configuration, architecture, actions, debugging, and more.
See docs/architecture.md for the module map and dependency rules.
See docs/forking.md if you want to customize pr-shepherd for your own use or team.