PR and MR workflows for GitHub (gh) and GitLab (glab). Covers creation, review comment handling, line-specific comments, review state queries, and merging
Primary skill for pull request and merge request workflows across GitHub and GitLab. Covers the full lifecycle: creation, review queries, comment handling, line-specific comments, and merging. All recipes use minimal field sets for token efficiency.
Before starting any PR workflow, detect the current state. This determines the right action:
# Check if a PR exists for the current branch (allowlisted, zero approval)
gh pr view --json number,state,reviewDecision,reviewRequests,title 2>/dev/null
| Result | Next action |
|--------------------------------------------|------------------------------------------------------------|
| PR exists, CHANGES_REQUESTED | Fetch unresolved threads (see Review Comment Handling) |
| PR exists, REVIEW_REQUIRED or has pending reviewRequests | Check review state or wait for reviewers |
| PR exists, a bot pending in REST requested_reviewers (Copilot shows as Copilot) | Auto-review in flight -- wait for it (Bot Review Loop); do NOT re-request |
| PR exists, unresolved review comments | Address the comments first (Review Comment Handling); never request a new review over outstanding ones |
| PR exists, APPROVED | Check CI status or proceed with merge |
| PR exists, no review decision yet | Check CI status, review state, or push more changes |
| No PR for current branch | Create a PR (see PR/MR Creation) |
This avoids offering to create a PR when one already exists, and immediately surfaces pending review work. The reviewDecision field reliably indicates whether a reviewer has requested changes without needing to fetch individual threads.
Auto-review is an optional setting -- never assume it either way (GitHub): automatic Copilot code review comes from either a repo/org ruleset or the author's personal Copilot setting, so its presence varies between accounts, orgs, and even repos of the same owner. When enabled it requests Copilot on every non-draft PR at creation (and when a draft is marked ready for review), so a freshly created PR may already have a pending bot request or bot comments minutes later -- and on other repos nothing fires at all. The ruleset source is detectable read-only (the copilot_code_review rule via gh api repos/{owner}/{repo}/rules/branches/{branch}; see references/copilot-review-config.md), but the personal setting has no API -- so an empty ruleset check still means detect, don't assume: check for a pending request or an existing bot review first, and only request one if neither shows up; otherwise you get a duplicate round. Gotcha: the pending bot request is only visible via gh api repos/{owner}/{repo}/pulls/{n}/requested_reviewers (login Copilot) -- gh pr view --json reviewRequests omits bot reviewers and stays empty.
Bot reviews cost money (Copilot) or quota (CodeRabbit) -- budget rounds deliberately. Every Copilot review, including each re-request, bills fully: 13 premium requests on legacy annual plans (up to ~23 reviews/month on Pro -- the pool is shared with all premium features), or token-metered AI credits + Actions minutes on current plans. CodeRabbit PR reviews draw from an hourly per-plan bucket that is per developer, not per PR -- all of a developer's open PRs contend for the same review windows. Check the project's Code Review Policy (below) before requesting anything billable; prefer local CodeRabbit CLI reviews (see the coderabbit skill) to iterate cheaply before the PR-side review.
gh api / glab api for operations not covered by subcommands (line comments, thread resolution, GraphQL).--json field1,field2 with gh to filter output. This IS the efficiency mechanism -- no --jq needed for basic queries. Only request fields you actually need.glab has no --json field1,field2 equivalent. Use -F json | jq '{fields}' to filter output for token efficiency.auto_incremental_review are on (both default), git push is the review request and it reviews whatever is on the branch at that moment. Do every task of the round first -- all fixes, tests, docs, local checks (coderabbit review --committed), everything committed -- then push once. A progress push spends a scarce per-developer review window on code you already intend to change. While the change is still moving, keep the PR a draft (drafts are not auto-reviewed, so pushes to them are free); marking it ready is the review request. Full gate: references/bot-review-loop.md (One Push Per Round).success either way -- the outcome is only in its description (Review completed vs Review rate limited), and a rate-limited round often posts nothing else at all: no review, no threads, no comment. Read it with gh pr checks --json name,state,bucket,description (the field is not returned by default, and gh pr view --json statusCheckRollup drops it entirely) before reporting a PR as reviewed.git remote get-url origin
| Remote URL contains | Provider | CLI | PR term |
|------------------------------------|----------|--------|---------|
| github.com | GitHub | gh | PR |
| gitlab.com or self-hosted GitLab | GitLab | glab | MR |
If ambiguous or both present, ask the user.
The glab recipes follow GitLab's documentation and are not exercised against a live instance -- the GitHub ones are. Treat flags and JSON shapes on the GitLab side as documented-but-unverified: check glab <command> --help before relying on one in an unattended flow, and prefer -F json | jq over assuming a field exists.
view, list, status, diff, checks, searchcreate, edit, merge, close, reopen, comment, review, approveReference: See
references/allowlist.mdfor tiered auto-approval patterns.
GitHub:
gh pr view --json number,title,state,isDraft,reviewDecision,mergeable,baseRefName,headRefName
GitLab:
glab mr view -F json | jq '{iid:.iid,title:.title,state:.state,draft:.draft,merge_status:.merge_status,target:.target_branch,source:.source_branch}'
GitHub:
gh pr view {number} --json number,title,state,isDraft,reviewDecision,mergeable,baseRefName,headRefName
GitLab:
glab mr view {iid} -F json | jq '{iid:.iid,title:.title,state:.state,draft:.draft,merge_status:.merge_status,target:.target_branch,source:.source_branch}'
Reference: See
references/review-queries.mdfor advanced review queries: per-reviewer state, approval checks, pending reviewers.
GitHub:
gh pr view --json reviews,reviewRequests,latestReviews
GitLab:
glab mr view -F json | jq '{upvotes:.upvotes,reviewers:[.reviewers[]?.username]}'
For detailed approval info (GitLab):
glab api projects/{project_id}/merge_requests/{iid}/approvals | jq '{approved:.approved,approvers:[.approved_by[]?.user.username]}'
GitHub:
gh pr diff --name-only
GitLab:
glab mr diff
GitHub:
gh pr view --json files
GitLab:
glab api projects/{project_id}/merge_requests/{iid}/changes | jq '[.changes[] | {path:.new_path,added:.diff | split("\n") | map(select(startswith("+"))) | length,removed:.diff | split("\n") | map(select(startswith("-"))) | length}]'
GitHub:
gh pr list --json number,title,author,reviewDecision,updatedAt
GitLab:
glab mr list -F json | jq '[.[] | {iid:.iid,title:.title,author:.author.username,updated:.updated_at}]'
GitHub:
gh pr view --json commits
GitLab:
glab api projects/{project_id}/merge_requests/{iid}/commits | jq '[.[] | {sha:.short_id,title:.title}]'
GitHub:
gh pr list --search "review-requested:@me" --json number,title,url
GitLab:
glab mr list --reviewer=@me -F json | jq '[.[] | {iid:.iid,title:.title,url:.web_url}]'
| Action | GitHub | GitLab |
|-------------------|--------------------------------------------------------|-----------------------------------------------------------|
| Create draft | gh pr create --draft --fill | glab mr create --draft --fill |
| Create with title | gh pr create --title "feat: ..." --body "..." | glab mr create --title "feat: ..." --description "..." |
| Create + request Copilot review | gh pr create --title "..." --body "..." --reviewer @copilot (gh >= 2.88) | n/a |
Keep bodies lean. The title follows Conventional Commits (git-commit skill); the body is a few sentences or tight bullets on what changes and why -- no restating the diff, no boilerplate sections beyond what the repo's PR template requires.
After creating a non-draft GitHub PR, check gh api repos/{owner}/{repo}/pulls/{n}/requested_reviewers before requesting any bot review -- repos with automatic Copilot review already have one in flight (and it will NOT show in gh pr view --json reviewRequests). On a repo you know has no auto-review, skip the create-then-edit round-trip and request Copilot at creation with --reviewer @copilot; when unsure, create normally and let the detection decide.
| Action | GitHub | GitLab |
|--------------|---------------------------------------|-----------------------------------------------|
| Squash merge | gh pr merge --squash --delete-branch | glab mr merge --squash --remove-source-branch |
| Rebase merge | gh pr merge --rebase --delete-branch | glab mr merge --rebase --remove-source-branch |
Reference: See
references/pr-comment-workflow.mdfor the full opinionated workflow with all command patterns and examples.
The workflow has two distinct phases -- never mix them:
Phase 1: Analyze and Fix (local work, no GitHub API writes) -- the fetches are zero-approval; git commit/git push are writes and stay a human checkpoint
--jq filter, then the bot's review bodies -- threads are not the whole review (see below)git-commit skill)Not every finding is a thread (CodeRabbit). Nitpicks (under profile: chill), outside-diff-range findings, duplicates and failed-to-post comments exist only in the review body; reviewThreads never returns them, which is the usual reason a review looks handled but isn't. Fetch the bot's review bodies, triage those items too, and reconcile the top-level bot threads against the Actionable comments posted: N the reviews claim -- commands in references/bot-review-loop.md.
Phase 2: Reply and Resolve (one batched command, one approval)
6. Combine all replies and all resolves into a single &&-chained command
7. REST replies first, then a single GraphQL mutation with aliases to batch-resolve all handled threads
8. Leave "Needs discussion" threads unresolved
This ordering matters: pushing fixes first ensures reviewers see the changes when they read replies. Never reply to a comment claiming "Fixed" before the fix is actually pushed.
GitHub -- one command with $(...) substitution. Generate as a single line and do NOT prepend variable assignments (OWNER=..., REPO=...) -- both break allowlist matching:
gh api graphql -f query="{ repository(owner: \"$(gh repo view --json owner --jq '.owner.login')\", name: \"$(gh repo view --json name --jq '.name')\") { pullRequest(number: $(gh pr view --json number --jq '.number')) { reviewThreads(first: 100) { totalCount pageInfo { hasNextPage endCursor } nodes { id isResolved isOutdated path line startLine comments(first: 20) { nodes { id fullDatabaseId body author { login } } } } } } } }" --jq '.data.repository.pullRequest.reviewThreads | {total: .totalCount, nextCursor: (if .pageInfo.hasNextPage then .pageInfo.endCursor else null end), unresolved: [.nodes[] | select(.isResolved==false)]}'
Returns the unresolved threads plus the two numbers that prove the fetch was complete. reviewThreads does not paginate on its own -- a non-null nextCursor means threads 101+ exist and may hold unresolved comments; re-run with reviewThreads(first: 100, after: \"{nextCursor}\") and merge before deciding anything is handled.
Response field mapping (critical -- using wrong ID causes silent failures):
| Field | Format | Use for |
|--------------------|-------------------------|------------------------------|
| thread .id | PRRT_... (node ID) | resolveReviewThread mutation |
| comment .fullDatabaseId | "2949637341" (string) | REST reply endpoint |
| comment .id | PRRC_... (node ID) | Not typically needed |
GitLab (REST):
glab api projects/{project_id}/merge_requests/{iid}/discussions --paginate | jq '[.[] | select(.notes[0].resolvable==true and .notes[0].resolved==false) | {id:.id,path:.notes[0].position.new_path,line:.notes[0].position.new_line,body:.notes[0].body,author:.notes[0].author.username}]'
GitHub -- combine all REST replies and a batch GraphQL resolve mutation into one &&-chained command. Reply uses fullDatabaseId (a string; databaseId is deprecated), resolve uses thread id (PRRT_ node ID):
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{fullDatabaseId_1}/replies -f body="Fixed in {sha} -- {explanation}" && \
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{fullDatabaseId_2}/replies -f body="Addressed in {sha}" && \
gh api graphql -f query="
mutation {
t1: resolveReviewThread(input: {threadId: \"PRRT_thread1\"}) { thread { isResolved } }
t2: resolveReviewThread(input: {threadId: \"PRRT_thread2\"}) { thread { isResolved } }
}"
GitLab:
glab api projects/{id}/merge_requests/{iid}/discussions/{disc_1}/notes --method POST --field "body=Fixed in {sha}" && \
glab api projects/{id}/merge_requests/{iid}/discussions/{disc_1} --method PUT --field "resolved=true" && \
glab api projects/{id}/merge_requests/{iid}/discussions/{disc_2}/notes --method POST --field "body=Addressed" && \
glab api projects/{id}/merge_requests/{iid}/discussions/{disc_2} --method PUT --field "resolved=true"
Review preferences are declared in a Code Review Policy section of an agent instructions file -- check for one before requesting any review. Two scopes, most specific wins:
~/.claude/CLAUDE.md) as the default flow across repos.Both reviewers are optional. Detect what is actually available (CodeRabbit app installed / CLI authenticated, Copilot ruleset or observed auto-review) and never assume a reviewer exists, is paid for, or should be added. Reviewer: none is a valid policy -- human review only. Example:
## Code Review Policy
- Reviewer: coderabbit # coderabbit | copilot | both | none
- CodeRabbit plan: pro+ until 2026-08-20, then pro # free | pro | pro+ | enterprise
- Copilot billing: legacy # legacy (premium requests) | credits
- Local review: coderabbit review --committed # run before every push
- PR review rounds: ask # ask | loop <= N
Declare the CodeRabbit plan -- no API reports it. coderabbit usage gives the billing period, not the tier, and the PR API never mentions it, so an agent that isn't told will either over-schedule PRs into a bucket that cannot serve them or idle a bucket that could. The plan sets the CEILING on the hourly allowance (Free 1 / Pro 5 / Pro+ 10 PR reviews per developer -- table in the coderabbit skill), but fair-usage throttling sets the effective rate beneath it from the developer's review count over a rolling 24-hour or 7-day window -- a heavy review week lands a Pro developer at 1--2/hour. Two surfaces do report the live figure: the run-configuration block inside each posted review (which also names the plan), and a @coderabbitai rate limit comment, which returns the remaining allowance and next availability without spending a review -- the cheap check before opening a loop. The allowance in turn sets how many PRs can be non-draft at once and how much iteration belongs in the local CLI lane. Note a trial's end date the same way: the queue that works on Pro+ stalls on Pro the morning the trial lapses. Undeclared, infer a floor instead of guessing -- bot_bucket (references/bot-review-loop.md) lists recent rounds across every open PR, and the completions in the trailing hour are a lower bound on the real allowance.
When no policy exists (either scope), default conservative: if an auto-review fired, process that round; then ask before any billable re-request -- and inform the recommendation with the quality of the round's findings (mostly valid substantive issues -> another round likely pays off; mostly noise -> stop). Never initiate billable reviews unprompted on repos without auto-review; loop only when explicitly asked.
Reference: See
references/bot-review-loop.mdfor the full loop: per-bot config blocks (Copilot, CodeRabbit), thebot_status/bot_tickdriver, polling, and termination logic. Reference: Seereferences/copilot-review-config.mdfor Copilot review configuration: billing models and quota checks, auto-review ruleset detection/management, custom instructions, and the Copilot CLI local review option.
GitHub review bots (Copilot, CodeRabbit) are GitHub-only, asynchronous (~minutes per review), and never block: their review state is always COMMENTED. The loop is identical per bot; only the identity (which login to filter), the re-request trigger, and the detectable failure/rate-limit/clean-review notices differ -- a per-bot config block sets them. Enablement detection is partial (the Copilot auto-review ruleset is readable, but personal-setting auto-review and overall bot availability are not -- see Context Check above), so bot_tick exits 5 when the remote isn't GitHub or the re-request errors; an accepted-but-unanswered request exits 3 (slow or silently unavailable).
Iterate until no valid comments remain. Source a bot's config + the bot_status/bot_tick driver -- one round is:
bot_tick {N} first checks for unresolved bot threads (handle those, never re-request over them), then a review at HEAD (a clean CodeRabbit review posts no review object -- its "no actionable comments" walkthrough text or a "Review finished." ack is the clean signal, never a failure or rate limit), then HEAD's CodeRabbit commit status, which is success even when the round was refused (Review rate limited = HEAD was never reviewed), then a pending request in REST requested_reviewers (auto-review on non-draft PR creation usually means round 1 needs no re-request at all). Only if none of those apply does it re-request (Copilot: gh pr edit {N} --add-reviewer "@copilot", gh >= 2.88, no auto re-review on push; CodeRabbit: a @coderabbitai review comment), then polls for the async review. Returns 0 clean / 2 not clean / 3 retry / 4 failed (already cooled down ~5 min + re-requested -- re-run to poll) / 5 not applicable / 6 rate-limited.Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer