Skip to content

Auto-PR workflow — Stop hook that ships and wakes you on review

Q16 · Parallelism & automation How are your PRs created when work finishes?

Max-score answer: “A Stop hook that auto-opens the PR and re-wakes me on review feedback.”

Why it matters: An auto-PR + review-fix loop turns “code written” into “PR shipped” without human bookkeeping in the middle.

In 2026 the bottleneck has moved. AI-assisted coding pushed PR volume up roughly 29% year over year, and the slowdown is no longer “writing the code” — it’s the bookkeeping ring around the code: branching off the right base, pushing, opening the PR with a sensible title, watching for the CodeRabbit summary, fixing the Copilot review nits, re-pushing, waiting on CI, and finally merging. A solo developer who writes a feature in 25 minutes can easily spend another 25 minutes on that ring. Multiply that by 8–12 PRs per day and the “shipping tax” becomes the single largest leak in your week. The agents became uncannily good at writing the patch; they remained terrible at handing off the patch unless you told them how, every single time. The Stop hook closes that gap. When the agent finishes substantive work, it doesn’t wait for “now make a PR” — it stages, commits, pushes, opens the PR via gh pr create, watches for review comments, and wakes the agent up again the moment something actionable lands. Combined with gh pr merge --auto --squash --delete-branch, the loop closes itself when CI goes green and reviews approve. That’s the difference between an agent that produces code and one that produces merged code.

You get the top mark on Q16 only when all five of these are true on a given day:

  • The agent doesn’t ask permission to ship. When substantive work produces commits or staged changes, the agent pushes a feature branch and opens the PR automatically — no “shall I open a PR now?” prompt. The decision lives in code (hook + workflow rule), not your working memory.
  • A Stop hook fires the workflow. When the agent’s main turn ends, a Stop hook (typically ~/.claude/hooks/auto-pr-watch.sh) runs, decides whether the repo needs a ship action, a fix action, or neither, and either drives the action or re-wakes the agent with a tightly-scoped prompt.
  • Review feedback wakes the agent back up. When CodeRabbit, Copilot Code Review, a human reviewer, or a failing CI check posts new comments after your last push, the hook detects new activity (by comparing comment IDs and head SHAs against a stored signature) and triggers a fix cycle. The agent reads every actionable comment, fixes, commits, and pushes — then waits a bounded interval (4 min → 12 min is the field-tested pair) for late comments.
  • Auto-merge is queued, not forced. Once the re-check loop settles, the agent runs gh pr merge --squash --delete-branch --auto. The --auto flag queues the merge for whenever required checks pass — it does not override branch protection, skip CI, or --admin past a real blocker. If checks fail or reviewers request changes, the loop surfaces the blocker.
  • The loop ends naturally. State is tracked per-repo as a signature (action, branch/PR#, head_sha, max_comment_id). Once the agent has acted on a state, the hook won’t re-wake it for the same state — only for genuinely new activity. The loop terminates because comments stop arriving.

Anything weaker — “I run a script that creates the PR but I check it manually,” or “I have a hook that opens a PR but doesn’t watch for reviews,” or “I let the agent merge whenever it feels like it” — is mid-tier on Q16.

Claude Code released its hooks system in early 2026 with 27 lifecycle events; the Stop hook fires whenever the agent finishes responding and is the natural hand-off point for auto-PR work. Hooks are deterministic shell commands wired into ~/.claude/settings.json — they aren’t asked by the model, they’re enforced by the harness. That’s the trick: hooks turn “the agent should remember to open a PR” (which it won’t) into “the harness opens the PR” (which it does). In parallel, GitHub Copilot Code Review and CodeRabbit became the two dominant review bots, and both post machine-readable feedback an agent can ingest. The pieces are there. The job is to wire them together.

Phase 1: Ship (commit, push, gh pr create)

Section titled “Phase 1: Ship (commit, push, gh pr create)”

The ship phase runs the moment the agent’s main turn ends with uncommitted work or unpushed commits. The hook script resolves the repo’s default branch (do not hardcode main — some repos use master, preview, develop), creates a feature branch if you’re sitting on the default, commits any staged work with a conventional message, and opens the PR.

Terminal window
# Resolve default branch — never hardcode.
default=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
current=$(git rev-parse --abbrev-ref HEAD)
# If we're on the default branch, branch off before pushing.
if [ "$current" = "$default" ]; then
git checkout -b "feat/$(date +%s)-auto"
fi
# Stage and commit (conventional message lifted from the diff).
git add -A
git commit -m "feat: <agent-summarised change>" || true
# Push and open the PR. gh defaults --base to the repo's configured base.
git push -u origin HEAD
gh pr create \
--title "feat: <agent-summarised change>" \
--body "$(cat <<'EOF'
## Summary
- <bullets lifted from commit messages>
## Test plan
- [ ] CI passes
- [ ] Manual smoke
🤖 Generated with Claude Code
EOF
)"

The critical rules: don’t pause for confirmation before pushing, don’t hardcode the base branch, and don’t use --no-verify to skip pre-commit hooks — if a hook fails, the commit didn’t happen, so fix the issue and create a new commit. Never --amend after a hook failure (it would silently rewrite the previous good commit).

Phase 2 is the part most people skip — and the reason their Q16 score caps at “I have a script that opens PRs”. The agent has to come back to the PR after the bots and humans land their notes. The hook handles the watching; the agent handles the fixing.

For each open PR your hook is responsible for, pull every feedback surface:

Terminal window
# Top-level conversation
gh pr view "$PR" --comments
# Inline review comments
gh api "repos/$OWNER/$REPO/pulls/$PR/comments"
# Review summaries (CodeRabbit, Copilot, human reviewers)
gh api "repos/$OWNER/$REPO/pulls/$PR/reviews"
# CI status
gh pr checks "$PR"
gh run view "$RUN_ID" --log-failed # for any failing job

Treat all four as in-scope: human reviewers, CodeRabbit (which now spawns its own coding agent via Autofix on Pro plans, April 2026), Copilot Code Review (multi-agent architecture, ~71% actionable feedback rate), and the GitHub Actions log. Skip acknowledgements and off-topic chatter.

For each actionable comment, the agent analyses the request, implements the fix, commits with a clear message, and pushes. Do not pause for confirmation. Do not squash-rewrite history — push new commits so reviewers see the deltas they asked for.

The re-check loop is the secret sauce. Late comments are the norm — CodeRabbit often follows up 30–90 seconds after your push, Copilot Code Review can take 2–5 minutes, and humans drift in over hours. The 4 min → 12 min backoff pair is field-tested: 4 minutes (240s) stays within the prompt-cache TTL so the agent doesn’t pay a cold-cache tax on the second pass; 12 minutes covers slower CI and human follow-up. If a wait surfaces new actionable comments, loop back and reset the backoff. If both waits return nothing, exit to Phase 3.

Phase 3: Auto-merge with --auto and --delete-branch

Section titled “Phase 3: Auto-merge with --auto and --delete-branch”

When the re-check loop settles with no actionable comments, check merge readiness and queue the merge:

Terminal window
# Are there blockers?
gh pr view "$PR" --json statusCheckRollup,reviewDecision,mergeable,mergeStateStatus
# Real blockers: FAILURE/CANCELLED checks, CHANGES_REQUESTED, CONFLICTING.
# Pending checks are NOT a blocker — --auto waits them out.
# Discover which merge methods the repo allows.
gh api "repos/$OWNER/$REPO" --jq '{squash:.allow_squash_merge,merge:.allow_merge_commit,rebase:.allow_rebase_merge}'
# Queue the merge.
gh pr merge "$PR" --squash --delete-branch --auto

--auto queues the merge for whenever required checks pass. If the repo doesn’t have auto-merge enabled and the flag errors, drop --auto and merge only when everything is already green. If --squash isn’t allowed, retry with --merge or --rebase. After the merge lands, restore a clean local state: git checkout "$default" && git pull --ff-only && git branch -d <merged-branch>. The lowercase -d refuses to delete unmerged work, so it’s safe.

On any real blocker (failing CI, conflicts, requested changes), surface it. Never --admin-override. That single line is the difference between an automation that helps and an automation that ships broken code on a Friday.

State tracking (signature files, deduping)

Section titled “State tracking (signature files, deduping)”

The hook needs to remember what it’s already done, or it will re-fire on every Stop event and either spam the PR or, worse, re-trigger the agent into an infinite loop. The pattern that works is a per-repo signature file in ~/.claude/auto-pr-state/<repo-hash>.json containing the tuple (action, branch/PR#, head_sha, max_comment_id). The hook computes the current signature, compares against the stored one, and:

  • If the signature is unchanged, do nothing — the agent has already handled this state.
  • If only max_comment_id advanced, wake the agent with a fix prompt.
  • If head_sha advanced or the PR is new, this is a fresh state — store it and decide whether to wake.

To reset a single repo: rm ~/.claude/auto-pr-state/<hash>.json. To clear all: rm ~/.claude/auto-pr-state/*.json. To opt out globally: comment out the hook in ~/.claude/settings.json.

A minimal working ~/.claude/settings.json entry:

{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/auto-pr-watch.sh" }
]
}
]
}
}

The auto-pr-watch.sh script itself is short: it reads repo state, computes a signature, decides between ship, fix, or noop, and uses asyncRewake (or your harness’s equivalent) to wake the agent with a tightly-scoped prompt like “Phase 2 fix: PR #421 has 3 new CodeRabbit comments, please address them.” The hook never does the work — it only notifies. Workflow logic stays in the agent’s prompt, where it belongs.

  1. Verify gh is installed and authenticated. Run gh auth status. If it reports nothing, run gh auth login. The Stop hook is useless without this.

  2. Pick the trigger states your hook will recognise. Minimum: ship (uncommitted work or unpushed commits on a non-default branch) and fix (an open PR has comments newer than the stored signature). Optional: merge and noop.

  3. Write ~/.claude/hooks/auto-pr-watch.sh. Make it executable (chmod +x). It computes the current signature, reads ~/.claude/auto-pr-state/<hash>.json, decides an action, writes the new signature back, and either drives gh directly or calls your harness’s wake-agent API.

  4. Wire the Stop hook into ~/.claude/settings.json. Use the JSON snippet above. Restart your Claude Code session so the config loads.

  5. Add a project-level workflow rule. In CLAUDE.md, document the Phase 1/2/3 contract explicitly: when work finishes, push and open the PR without asking; when review arrives, fix and re-push without asking; when the loop settles, queue auto-merge.

  6. Test with a throwaway PR. Pick a tiny change, let the agent finish, watch Phase 1. Add a comment yourself, wait, watch Phase 2 fire. Approve the PR, watch Phase 3 queue the merge.

  7. Tune the backoff to your review tempo. 4 min → 12 min is the sane default. With CodeRabbit Pro Autofix, add a third 25-minute wait. Solo, keep it tight.

  8. Add an opt-out phrase. “don’t make a PR for this” or “this is exploratory” — when you say it, the agent skips Phase 1. Safety valve for spikes and prototypes.

  • Pushing straight to the default branch. The single most expensive mistake. Always resolve the default with gh repo view --json defaultBranchRef --jq .defaultBranchRef.name and branch off it if you’re sitting on it. If commits leaked onto the default, reset it to origin before pushing — never force-push the default branch.
  • --no-verify to skip pre-commit hooks. If a hook fails, the commit didn’t happen. Skipping it silently lands broken code. Fix the underlying issue and create a new commit. Same rule for --no-gpg-sign — investigate, don’t bypass.
  • Force-pushing to main / master / preview. Never. Even with --auto-merge. If your hook reaches for git push -f against the default branch, kill it before it lands. Force-push to your own feature branch is fine; force-push to a shared branch is how you lose other people’s work.
  • --amend after a failing pre-commit hook. The commit didn’t happen, so --amend modifies the previous commit — usually destroying earlier work. Always create a new commit after a hook failure.
  • --admin-override on gh pr merge. Bypasses branch protection. Never the right move from an automated hook. If checks fail or reviewers requested changes, the loop must surface the blocker and stop.
  • Forgetting to dedupe with a signature file. Without a signature, the Stop hook fires on every turn and either spams the PR or wedges the agent into an infinite “Phase 2 again” loop. The signature is what terminates the loop.
  • Treating every comment as actionable. Acknowledgements (“LGTM!”) and off-topic chatter shouldn’t trigger a fix cycle. Filter for comments that explicitly request changes.
  • No opt-out phrase. Spikes and prototypes don’t want a PR. If you can’t say “skip Phase 1,” the hook will fight you on throwaway work.
  • You have a Stop hook script at ~/.claude/hooks/auto-pr-watch.sh (or your harness’s equivalent) and it’s wired into ~/.claude/settings.json.
  • When the agent finishes substantive work on a feature branch, a PR appears on GitHub without you running gh pr create manually.
  • The Stop hook resolves the default branch dynamically — you can move it from main to preview and the workflow still works.
  • When CodeRabbit, Copilot Code Review, a human reviewer, or a failing GitHub Actions check posts new comments, the agent wakes up and addresses them within ~5 minutes — without you re-pasting the comments.
  • After the re-check loop settles with no actionable comments, gh pr merge --squash --delete-branch --auto is queued automatically.
  • You have a per-repo signature file in ~/.claude/auto-pr-state/ and the hook does not re-fire on the same state twice.
  • You have an opt-out phrase (“don’t make a PR for this”) and it actually skips Phase 1.
  • You have never seen the hook reach for --no-verify, --admin, or git push -f to a shared branch. If you did, you killed the hook before it landed.