PR Takeover
Drive one pull request to merge: continuously read the live review state, fix what reviewers ask for, reply to and resolve threads, dismiss change requests you can justify as wrong, and merge once the PR is approved and all required checks pass — looping until it lands.
There are two ways to run this. Pick one in Step 0, then execute it.
| Mode | Who runs the agent | Lifetime | Needs |
|---|---|---|---|
| A · Live (default) | you, in this session | until your session ends | gh + git only |
| B · Background | the Looper daemon | survives you leaving; runs for days | Looper installed |
Step 0 — Choose the mode (confirm with the user only if unclear)
- If the user already signalled a preference, honor it:
- "in the background", "while I'm away", "even after I close this", "set and forget" → Mode B.
- "watch it", "do it now", "in here" → Mode A.
- Otherwise default to Mode A (zero install, uses your already-authenticated session). Mention in one line that Mode B exists for unattended runs, and switch only if they ask.
- Choose Mode B only if
command -v loopersucceeds and the user wants it unattended. If they want unattended but Looper isn't installed, tell them to install it (https://github.com/nexu-io/looper) or fall back to Mode A.
Keep this lightweight — ask at most one short question, and only when the user gave no signal.
Prerequisites
gh auth status # authenticated, with push access to the PR branch
git rev-parse --show-toplevel # run from inside the repo checkout
Identify the PR (explicit <owner>/<repo>#<num> wins; else the current branch's):
gh pr view --json number,headRefName,baseRefName,url
If no PR exists for the branch, stop and ask the user to open one.
Mode B — Background (Looper daemon)
Hand the PR to Looper and you're done; it runs the reviewer + fixer loops itself.
looper takeover <owner>/<repo>#<num> --merge # --merge enables auto-merge once approved + green
looper takeover list # check status later
looper takeover stop <owner>/<repo>#<num> # stop it
Scopes Looper to just this PR (it won't touch other PRs). Note: Looper runs its own agent headlessly, so its configured agent vendor must be authenticated for non-interactive use. Report the loop ids it prints, then stop — the rest happens in the background.
Mode A — Live (this session)
Loop until the PR is merged or a hard blocker needs a human. Each iteration:
1. Snapshot the live state (never act on stale data)
gh pr view <num> --json state,isDraft,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,headRefOid
List unresolved review threads:
gh api graphql -f query='
query($owner:String!,$repo:String!,$num:Int!){
repository(owner:$owner,name:$repo){ pullRequest(number:$num){
reviewThreads(first:100){ nodes{ id isResolved
comments(first:20){ nodes{ author{login} body path line } } } } } }
}' -F owner=<owner> -F repo=<repo> -F num=<num> \
--jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)
| {id, file:.comments.nodes[0].path, line:.comments.nodes[0].line, body:.comments.nodes[-1].body, author:.comments.nodes[0].author.login}'
Pick the iteration's action:
state==MERGED→ done.state==CLOSED→ stop, tell the user.- actionable unresolved threads → 2.
- none,
reviewDecision==APPROVED, all required checksSUCCESS,mergeable==MERGEABLE→ 4. - else (CI pending / awaiting re-review) → 5.
2. Fix each actionable thread
Make the change (scoped to what the thread asks — don't rewrite unrelated code), run quick tests/linters, then:
git add -A && git commit -m "fix: <addresses review comment>"
git push # if rejected non-fast-forward: git pull --rebase && git push. Never --force.
3. Reply, then resolve — or dismiss
Reply with how you fixed it, then resolve:
# reply
gh api graphql -f query='mutation($t:ID!,$b:String!){addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$t,body:$b}){comment{id}}}' -F t=<threadId> -F b="Fixed in <sha>: <summary>."
# resolve
gh api graphql -f query='mutation($t:ID!){resolveReviewThread(input:{threadId:$t}){thread{isResolved}}}' -F t=<threadId>
For a change request you can justify as incorrect or out of scope: reply with your reasoning first, then dismiss it (message is mandatory — state why):
# get the CHANGES_REQUESTED review's node id (PRR_…)
gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviews(first:50){nodes{id state author{login}}}}}}' -F o=<owner> -F r=<repo> -F n=<num> --jq '.data.repository.pullRequest.reviews.nodes[]|select(.state=="CHANGES_REQUESTED")'
# dismiss with a reason
gh api graphql -f query='mutation($id:ID!,$m:String!){dismissPullRequestReview(input:{pullRequestReviewId:$id,message:$m}){pullRequestReview{state}}}' -F id=<reviewNodeId> -F m="Dismissing: <why this change request is wrong/out of scope>."
3b. Re-request the reviewer after pushing a new head
GitHub does not auto-re-request a reviewer when you push fixes — their CHANGES_REQUESTED stays on the record, the PR keeps showing as blocked even though you addressed everything, and the reviewer gets no notification that there's a new head to look at. So after pushing fixes that address a reviewer's change request, re-request them:
# for each reviewer whose requested changes this push addresses
gh pr edit <num> --add-reviewer <login>
This is what flips a stale CHANGES_REQUESTED back into an active review request and notifies the reviewer. Skip only if they already re-reviewed the current head.
4. Merge when approved and green
Only when reviewDecision==APPROVED, every required check is SUCCESS (none pending/failing), mergeable==MERGEABLE, and not a draft:
gh pr merge <num> --squash --delete-branch # use --merge/--rebase to match repo convention
# or: gh pr merge <num> --squash --auto # let GitHub merge as soon as requirements are met
Then report and stop.
5. Wait, then re-loop
Poll on an interval instead of spinning. Use your agent's own loop/scheduler if it has one:
- Claude Code:
/loop 5m <this instruction>, or schedule a wake-up; idle between ticks. - Codex / opencode / Gemini / others: re-run this instruction on a timer, or iterate with a
sleep 180between checks.
Safety rails (apply even in full-auto)
- Never merge with a failing or pending required check. Green-and-approved is the only gate. Never
--admin-bypass. - Never force-push, rewrite others' commits, or delete the base branch.
- Dismiss only with a written reason. If you can't articulate why a change request is wrong, treat it as valid and fix it.
- Stop and ask a human when: someone says "hold" / "do not merge" / adds a hold label; a fix needs a product or design decision; the same thread reopens after 2 fix attempts; or a merge conflict needs real judgement.
- Cap no-progress loops: after N iterations with no new commit and no state change, stop and summarize the blocker.
More detail
Extended gh / GraphQL recipes: references/github-commands.md (when installed as a skill), or fetch
https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/references/github-commands.md.
One universal prompt (for a bot to post under a PR)
Works in any agent, with or without this skill installed — it just points the agent at this file:
Take over this PR until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.
If your agent can't fetch URLs, paste this instead:
Take over this PR until it merges. Loop: read the open review threads (
gh), fix what they ask and push, re-request the reviewer after each push (GitHub won't auto-re-request, so the reviewer won't otherwise know there's a new head), reply + resolve each thread, dismiss any change request you can justify as wrong (with a written reason), and merge once it's approved and all required checks are green. Never merge on red/pending checks, never force-push, and stop to ask me if someone says hold or a fix needs a product decision.