Skip to content

jonathanong/pr-shepherd

Repository files navigation

pr-shepherd

Autonomous PR CI monitor and review-comment resolver for Claude Code.

Why pr-shepherd

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=json and --format=text surface 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

Design principles

  • 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=json and --format=text carry equivalent information; every field in one has a representation in the other

Features

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.

What the CLI does

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 query
  • resolve — fetch/triage mode auto-resolves outdated threads and returns actionable items (each annotated with any parseable ```suggestion block); mutate mode batch-resolves threads, minimizes comments, and dismisses reviews by ID, polling --require-sha so reviewers see the push before threads close
  • commit-suggestions — agent-side equivalent of GitHub's "Commit suggestion" / "Add suggestion to batch" buttons: parses reviewer ```suggestion blocks, creates a single remote commit via createCommitOnBranch (co-crediting each reviewer), and resolves the applied threads
  • iterate — 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.

What the skills do

Claude Code skills that wrap the CLI with model-driven triage, code edits, and flow control:

  • /pr-shepherd:check — calls check --format=json and 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 /loop cron job (4-minute default, 8-hour expiry, 50-turn cap), deduplicates via a # pr-shepherd-loop:pr=<N> tag in CronList, dispatches on the [ACTION] H1 tag each tick, runs rebase scripts and fix instructions in the main conversation
  • /pr-shepherd:resolve — runs resolve --fetch, triages every returned item into Fixed / Actionable / Not-relevant / Outdated / Acknowledge (no silent drops — this is a project invariant), prefers commit-suggestions for threads carrying a ```suggestion block so the reviewer's change is applied verbatim and co-credited, applies any remaining edits by hand, cancels stale CI runs, pushes, then calls resolve in mutate mode with --require-sha

See docs/skills.md for full skill reference.

Install

Note: Skill and plugin install methods add the skill definitions only — they do not install the pr-shepherd CLI. The skills invoke npx pr-shepherd, so you also need the CLI available. If you're using pr-shepherd as development tooling for your repo, install it as a dev dependency so npx resolves it without prompting:

npm install --save-dev pr-shepherd

A plain npm install pr-shepherd adds it to regular dependencies instead; use that only if you specifically want it under dependencies. Or install globally: npm install -g pr-shepherd.

As individual skills via npx skills

npx skills add jonathanong/pr-shepherd

Installs 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.

As a Claude Code plugin (recommended)

claude /plugin marketplace add jonathanong/pr-shepherd
claude /plugin install pr-shepherd

This 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.

Without the plugin — custom slash command

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.

  1. Create the command file:

    • Project-scope: .claude/commands/pr-check.md
    • User-scope: ~/.claude/commands/pr-check.md
  2. 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
  3. 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.

As a global CLI

npm install -g pr-shepherd

Usage

Monitor a PR

Creates 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

Check a PR

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

Resolve review comments

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.

Workflow

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.

CLI

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 table

Common 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

pr-shepherd check [PR]

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-cache

Exit 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

pr-shepherd resolve [PR]

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=json
Actionable 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.

pr-shepherd commit-suggestions [PR] --thread-ids A,B,…

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=json

Requires 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.

pr-shepherd iterate [PR]

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-cancel

See 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

pr-shepherd status PR1 [PR2 …]

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.

Configuration

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 commits

All 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.

Requirements

  • Node.js ≥ 22.0.0
  • A GitHub token: set GH_TOKEN or GITHUB_TOKEN, or install and authenticate the gh CLI (gh auth login) — pr-shepherd uses gh auth token as a fallback. The repo scope is required for private repositories.
  • git

Docs

Full reference: docs/README.md — CLI usage, skills, configuration, architecture, actions, debugging, and more.

Architecture

See docs/architecture.md for the module map and dependency rules.

Forking

See docs/forking.md if you want to customize pr-shepherd for your own use or team.

License

MIT

About

Automatic Pull Request Autofixing for Claude Code

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors