Merge Ready
Take an open PR from wherever it is to “ready to merge”, and report exactly what still needs a human when automation can’t finish the job.
Steps
-
Detect the forge and pick the CLI: Read the remote (
git remote get-url origin) and choose the matching CLI, verifying it is installed and authenticated before proceeding:Host in remote URL CLI PR concept Auth check github.com / GHE ghpull request gh auth statusgitlab.com / self-hosted GitLab glabmerge request (MR) glab auth statusGitea / Forgejo / Codeberg teapull request tea login listThe steps below are written with
gh; on another forge, translate each operation to the equivalent verb (gh pr view->glab mr view --output json/tea pr <n>,gh pr comment->glab mr note,gh run view->glab ci view,gh pr update-branch->glab mr rebase, review threads ->glab api projects/:id/merge_requests/:iid/discussions). Confirm exact flags with--helprather than guessing. If the forge has no CLI (e.g. Bitbucket), fall back togitplus the forge’s REST API viacurl, and list anything you cannot automate as a blocker in the report. -
Identify the PR: Resolve the argument (number or URL) or the current branch’s PR via
gh pr view. If no PR exists, stop and suggest theprskill. If it is a draft, continue fixing but only mark it ready (gh pr ready) at the end, once everything else passes. -
Assess: Take one snapshot of the PR state:
gh pr view <n> --json number,title,body,url,state,isDraft,mergeable,mergeStateStatus,reviewDecision,reviewRequests,latestReviews,statusCheckRollup,baseRefName,headRefName,maintainerCanModify,closingIssuesReferencesThen fetch unresolved review threads (only GraphQL exposes resolution):
gh api graphql -f owner=<owner> -f repo=<repo> -F pr=<n> -f query=' query($owner:String!,$repo:String!,$pr:Int!){ repository(owner:$owner,name:$repo){ pullRequest(number:$pr){ reviewThreads(first:100){ nodes{ id isResolved isOutdated path line comments(first:20){ nodes{ author{login} body } } }} }} }'Then discover the merge gates:
gh api repos/{owner}/{repo}/rules/branches/<base>lists required status checks, required approving review count, and whether code-owner review is required (readable without admin). If that returns nothing, infer gates frommergeStateStatusandreviewDecision.Build a blocker list and split it in two: fixable by automation (conflicts, failing checks, unaddressed comments, stale description, missing bot trigger, draft state) vs human-only (required approving reviews from people or CODEOWNERS, org settings, missing secrets).
-
Sync with base: If
mergeStateStatusisBEHIND, rungh pr update-branch <n>; if that fails, merge the base locally and push. IfDIRTY(conflicts), check out the branch,git fetch originandgit merge origin/<base>, resolve each conflict by reading the surrounding code and preserving the intent of both sides (never resolve by blindly taking one side), then commit the merge and push. Never force-push. -
Get CI green: For each failing entry in
statusCheckRollup, pull the logs (gh run view <id> --log-failed), diagnose the root cause, fix it, commit conventionally, and push (thefix-and-retryworkflow). If a failure is flaky or external (network, rate limit), re-run it instead:gh run rerun <id> --failed. For third-party bot checks that re-trigger via a comment command, find the documented phrase (step 7) rather than pushing an empty commit. -
Address review comments: For each unresolved, non-outdated thread:
-
Actionable code feedback: apply the fix, commit, push, reply briefly to the thread stating what changed, then resolve it via GraphQL:
gh api graphql -f id=<threadId> -f query=' mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread{ isResolved } } }' -
Question: answer it in a reply; resolve only if fully answered.
-
Disagreement or judgment call: reply with your reasoning and leave the thread unresolved for the human to close.
-
-
Bot approvals and re-reviews: If a required check or review comes from a bot that is triggered by a comment (Claude, CodeRabbit, Copilot, CLA bots,
/retest-style CI bots), find its exact trigger phrase from prior comments on this PR, recent merged PRs in the repo, or CONTRIBUTING/README, then post it:gh pr comment <n> --body '@claude review'. Never invent a trigger phrase; if none is discoverable, list the bot as a blocker in the report. If a bot approved before you pushed new commits, its approval may be stale; re-trigger it after your last push. -
Issues and description hygiene:
- Linked issues: check
closingIssuesReferences; if empty, look for an issue number in the branch name, commit messages, or by searching open issues for the feature/bug. If the PR clearly fixes one, addCloses #<n>to the body. - Title: conventional-commit style, under 70 chars, describing the current diff (it may have drifted as commits were added).
- Body: ensure a
## Summaryreflecting the full current diff and a## Test plan. Augment what a human wrote rather than rewriting it. Apply withgh pr edit <n> --title ... --body ...(HEREDOC for body).
- Linked issues: check
-
Re-assess and loop: After pushes, repeat step 3. Wait for new CI runs by matching
headShato the new HEAD (see gotchas), not by watching the latest run. Loop until every fixable blocker is cleared or only human-only blockers remain. Then, if the PR is a draft and everything else is green,gh pr ready <n>. -
Report using this template:
## PR readiness: <url>
**Ready to merge: YES | NO**
Fixed this run:
- <conflict resolved / check fixed / N threads addressed / description updated>
Remaining blockers (need a human):
- <e.g. 1 approving review required from CODEOWNERS: @alice or @bob>
- <e.g. stale approval dismissed by my push; re-request from @carol>
- <e.g. required check "license/cla" needs the author to sign the CLA>
Rules
- Never merge the PR, enable auto-merge, force-push, amend, or rebase published commits.
- Never resolve a review thread you did not actually address, and never dismiss someone’s review.
- Only fix what you can confidently diagnose from logs, diffs, or comments; otherwise report the diagnosis as a blocker instead of guessing.
- Do not ping or re-request human reviewers unless the user asked; list them in the report instead.
- If the fix needs secrets, repo settings, or permissions you lack, say exactly what is needed rather than attempting workarounds.
Gotchas
mergeable: UNKNOWNmeans GitHub is still computing it asynchronously; re-query after ~5 seconds, up to a few tries, before acting on it.mergeStateStatusdecoder:BEHIND= needs base update,DIRTY= conflicts,UNSTABLE= a non-required check is failing (technically mergeable, still fix it),BLOCKED= required approvals or checks missing. Draft PRs showBLOCKEDno matter what, so checkisDraftfirst.- REST comment endpoints do not expose thread resolution; only the GraphQL
reviewThreadsquery hasisResolved/isOutdated, and theresolveReviewThreadmutation needs the thread node id from that query, not a comment id. - Pushing new commits can auto-dismiss stale approvals (a branch-protection
setting). Re-check
reviewDecisionafter every push and surface any re-approvals needed in the final report. - A new CI run is not registered the instant you push. Capture
git rev-parse HEAD, then pollgh run list --branch <head> --json databaseId,headSha,status,conclusionand watch the run whoseheadShamatches; watching--limit 1blindly can latch onto the previous run. - Fork PRs: if
maintainerCanModifyis false and you lack push access to the head branch, you cannot push fixes; describe the needed changes in a PR comment and list them as blockers instead. gh pr update-branchfails when the repo disallows the update-branch button or on some fork setups; fall back to the localgit merge origin/<base>+ push path.