The source-control skill bodies name operations (open a draft change request, read its checks,
reply on a review thread). This file is how GitHub, the one forge the plugin supports today, does
each of them, with the gh CLI. Read it before the first forge operation a skill performs. The
decision that skill bodies stay forge-neutral and the provider detail lives here is
ADR 0060.
This file is the index from an operation to its command; a skill's spoke is the authority on the
full step around it. A spoke whose steps need more than a table row has a provider file of the same
name under a directory named for its skill: pull-request/ holds the GitHub
commands, scripts and gh facts behind the pull-request skill's create.md, ready-for-review.md,
monitor.md, readiness.md, merge.md and review-venue.md, and each spoke links its steps there. The worktree
skill's spokes need only the rows below. The babysit-prs and babysit-loop skills' spokes link
their commands in babysit-prs/ and babysit-loop/.
A pull request is GitHub's change request; this file uses its terms.
| Operation | GitHub command |
|---|---|
| The current login | gh api user --jq .login |
The current repository as owner/repo |
gh repo view --json nameWithOwner -q .nameWithOwner |
| The repository's default branch | gh repo view --json defaultBranchRef -q .defaultBranchRef.name; in a sandboxed session, gh api "repos/{owner}/{repo}" --jq '.default_branch' |
| Read a tracked file from a repository's default branch | gh api repos/<owner>/<repo>/contents/<path> (the content field is base64) |
| Operation | GitHub command |
|---|---|
| The current branch's PR number | gh pr view --json number -q .number |
| The same, with the branch named (ambiguous or stale checkout) | gh pr view "$(git branch --show-current)" --json number -q .number |
| State for the smart default | gh pr view --json state,number,isDraft (non-zero exit: no PR) |
| The current branch's PR with author and head branch (babysit-prs single-PR scope) | gh pr view --json number,url,author,headRefName |
Number, URL and state for status |
gh pr view <N> --json number,url,state |
| Head commit | gh pr view <N> --json headRefOid -q .headRefOid |
| Mergeability | gh pr view <N> --json mergeable; CONFLICTING means GitHub runs no workflows until the base is integrated |
| The PR for a branch | gh pr list --head <branch> --json number,title,state |
| Whether a branch's PR merged (worktree cleanup) | gh pr list --state merged --head <branch>; a non-empty result means merged |
| PRs for worktree cross-reference (one call per worktree branch, all states) | gh pr list --head <branch> --state all --json number,title,state,headRefName; a PR whose head is <branch> is a match, and a failed call marks that branch's PR lookup incomplete |
| Your own open PRs here | gh pr list --state open --author "@me" --json number |
| Your own open PRs across an owner | gh search prs --state open --author @me --owner <owner> --json repository |
gh pr view with no number resolves by the checked-out branch. Resolve the number once at phase
entry and pass it to every later gh call in that phase.
| Operation | GitHub command |
|---|---|
| Open a draft PR | gh pr create --draft (with --head <branch> on the PR-only entry), never --reviewer |
| Check out the PR head | gh pr checkout <N>, or gh pr checkout <N> --detach when the branch is locked in another worktree. It is fork-safe: a fork's head branch is not fetchable from origin by name |
| Merge the base into the head | gh pr update-branch <N> (pull-request ready-for-review.md) |
| Flip a draft ready | gh pr ready <N>, run only inside /source-control:pull-request ready, never bare |
| Merge a PR | gh pr merge <N> -R <owner>/<repo> with the PR number and repository written literally, plus the flags the repository needs: --squash, --auto on a merge-queue or auto-merge base, --admin only where a code-owner review or merge queue blocks an otherwise ready merge and the consumer's policy allows it; --match-head-commit <sha> is optional for an immediate merge and required with --auto. A refused merge is reported, never handed to the user or retried in another shape (pull-request/merge.md; pull-request merge.md §4.2.1 owns when to merge without asking). A babysit lane merges only through its merge-gate wrapper (babysit-prs safety.md) |
| Open a tracking issue | gh issue create |
| Operation | GitHub command |
|---|---|
| List the PR's checks | gh pr checks <N>; never --watch, which blocks the foreground |
| CI logs, last resort | gh run view --log-failed, which truncates around 4 MB at the CLI display layer (cli/cli#11059, #10551, #7771); the fetch-logs action's annotation and archive paths return complete data |
A workflow that exchanges an OIDC token fails when the PR modifies that workflow file: the file must match the default branch for the exchange to succeed. Classify that failure as informational.
GitHub splits PR feedback across three surfaces, each paginated at 30 items per page, oldest first.
Pass --paginate and per_page=100 on every read, or the newest item (usually your own reply) is
missed.
| Surface | Read |
|---|---|
| Inline review-thread comments | gh api --paginate "repos/<owner>/<repo>/pulls/<N>/comments?per_page=100" |
| Conversation (issue-level) comments, where AI-review summaries land | gh api --paginate "repos/<owner>/<repo>/issues/<N>/comments?per_page=100" |
| Reviews (bodies of APPROVED, CHANGES_REQUESTED and COMMENTED reviews) | gh api --paginate "repos/<owner>/<repo>/pulls/<N>/reviews?per_page=100" |
Round scoping: inline comments carry original_commit_id, reviews carry commit_id, and
conversation comments carry only created_at.
| Operation | GitHub command |
|---|---|
| React to a comment | POST to the comment's /reactions endpoint (+1, -1, eyes); verify with a GET on the same endpoint. Review bodies have no reactions endpoint |
| Reply on an inline thread | gh api repos/<owner>/<repo>/pulls/<N>/comments/<id>/replies -F body=@<body-file> |
| Reply on the conversation | gh pr comment <N> --body-file <body-file> |
| Verify an inline reply | the inline surface filtered with --jq '.[] | select(.in_reply_to_id == <original-id>)' |
| Verify a conversation reply (D5, before any fix) | the conversation surface filtered with --jq '.[] | select(.user.login == "<posting-identity>") | .body' |
| Verify a follow-up reply citing the fix | the conversation surface filtered with --jq '.[] | select((.body | contains("<sha>")) and .user.login == "<posting-identity>") | .body', never .[-1] |
| Tell a bot from a person | REST user.type == "Bot"; GraphQL author.__typename == "Bot" |
| Resolve a review thread | the GraphQL resolveReviewThread mutation, then confirm isResolved == true over GraphQL |
Write a reply body to a file with the Write tool and pass the file: a reply often quotes reviewer
text, and a quote, $(…) or backtick inside it can break out of an inline shell argument.
| Operation | GitHub command |
|---|---|
| Effective rules on a branch | gh api repos/<owner>/<repo>/rules/branches/<branch> |
| Classic protection fallback | gh api repos/<owner>/<repo>/branches/<branch>/protection |
| Whether head branches are deleted on merge | the repository's delete_branch_on_merge field: gh api repos/{owner}/{repo} --jq '.delete_branch_on_merge' |
A cloud session behind the GitHub proxy is served no GraphQL operation: every GraphQL request gets
HTTP 403, whatever token is set. Pointer:
Configure cloud environments § GitHub proxy.
As-of: 2026-10-11. Recheck trigger: that section changes its GraphQL entry, or a release note names
the GitHub proxy. gh pr view --json, gh repo view --json, gh pr create, gh pr ready
and gh pr merge all run over GraphQL. The 403 reads like an expired token or a missing scope and
is neither: switch to the REST route the spoke names rather than re-authenticating. Probe it
read-only:
gh api graphql -f query='query{viewer{login}}' --jq '.data.viewer.login'A login means GraphQL is served. Under the restriction, the babysit engine re-sources the
gh pr view bundle over REST (GET …/pulls/{n} plus the commit check-runs and combined-status
endpoints), so discovery, classification, branch-rule and freshness checks keep working.
Review-thread resolution has no REST equivalent and is not approximated: the merge gate reports
threadResolutionProven: false and unresolvedThreadCount: null and holds the PR as readiness
UNPROVEN. reviewDecision degrades the same way, because REST can prove CHANGES_REQUESTED but
not an approval (GitHub folds CODEOWNERS and the required-reviewer count into that field). The same
restriction is handled in the pull-request skill's create.md §2.4.0 and the
work-item-tracker GitHub adapter.
A draft cannot merge until it is marked ready (changing the stage of a pull request, read 2026-10-11; recheck when GitHub changes the draft-stage rules).
Every gh call by every agent and session under one user shares that user's hourly budget: 5,000
REST requests and 5,000 GraphQL points. gh never retries or backs off. Every unattended loop
follows this cadence:
- Which bucket. GraphQL:
gh pr view,gh pr list,gh pr checks(--watchre-queries every 10 s),gh pr create,edit,readyandmerge,gh issue,gh api graphql. REST:gh run,gh api <path>. - Waiting. One watcher per PR or run, in the background, polling at 60 s or longer:
gh run watch <id> --exit-status -i 60(the default is every 3 s), or REST reads ofrepos/<owner>/<repo>/pulls/<n>andcommits/<sha>/check-runs. Never loopgh pr viewand never rungh pr checks --watch. 60 s is a floor per watcher, not a budget: with several watchers, poll every 2 s × watchers × requests per poll or slower, which keeps them near a third of the hourly REST budget (ten pull-request monitors at about seven requests a poll: 140 s, set as the monitor'sINTERVAL). - GraphQL-only fields (
isInMergeQueue,statusCheckRollup): read once, at the point you decide. - Writes. One at a time, at least 1 s apart, never in a retry loop. Secondary limits cap content-creating requests at 80 a minute and 500 an hour, and all requests at 100 concurrent.
- Rate-limit errors. Stop at the first one; never retry in a loop or probe for recovery. Wait
until
x-ratelimit-reset, orretry-afterwhen a secondary limit sends it, else at least one minute, then back off exponentially and give up after a few tries.gh api rate_limitdoes not show secondary limits and can count against them.
Pointer: best practices for using the REST API,
REST rate limits,
GraphQL rate limits.
As-of: 2026-10-17. Recheck trigger: either rate-limits page changes a figure, or a gh release
changes the run watch or pr checks --watch interval or adds retries.