# GitHub - gh CLI and API Usage Scripted work with `gh` and the GitHub API fails in predictable ways: tables parsed as text, rate-limit buckets confused, and state guessed instead of checked. Watch exit codes, respect separate rate-limit buckets and pagination caps, and verify state through the API. Account selection is its own concern: `gh` keeps one active account per host, and some enterprise tokens can read but not write. ## Key points - Gate scripts on `gh pr checks` exit codes, not table text: 0 pass, 8 pending, 1 fail. Aggregate gates park at pending on draft PRs by design. - `gh pr checks <PR>` aggregates all check-run statuses in one call. Loop over PR numbers for the CI picture across many PRs. - `gh pr checks --watch` blocks until CI settles but exhausts the 5,000-call GraphQL quota. The REST check-runs endpoint returns the same data without the limit. - GraphQL rate-limit errors are transient and use a bucket separate from the REST core limit of 5,000 per hour. A healthy core limit says nothing about the GraphQL bucket. - `gh pr checks` conflates cancelled jobs with failures. Per-job `conclusion` from `gh run view --json jobs` separates red jobs from fail-fast kills. One platform green and the other red on the same commit points to a nondeterministic test. - `gh run rerun <run-id> --failed` retries only the failed and cancelled jobs. Use it for infra flakes, not the whole matrix. - On a 403 or 429 containing "rate limit": query `gh api rate_limit`, sleep until `resources.core.reset`, retry once, and abort before writing state. - The Actions runs API treats any `exclude_pull_requests` value, including `false`, as true, silently returning no PR feedback. Omit the parameter and pin that with a test. It also caps at 1,000 results per query and returns an empty page past it. - Read a repo file without cloning: `gh api repos/<owner>/<repo>/contents/<path> --jq '.content' | base64 -d`. `gh search code` misses dotted numeric literals, so negative evidence needs the file read. - Scan an org's maintainer lists without cloning: `gh api /orgs/<org>/repos --paginate --jq '.[].name'`, then fetch each MAINTAINERS.md via the contents API. - `has_security_policy` is set by an org-level SECURITY.md, so a repo can show `true` with no local policy file. Check with `gh api repos/<owner>/<repo> --jq '.has_security_policy'`. - The PR creation cap (`interaction-limits/pulls/creation-cap`) is forward-looking: lowering it never closes open PRs, and drafts never count. Change it with a PATCH that sends `-F enabled=true -F max_open_pull_requests=N`. - Compare action tags before you request a pin bump. A workflow file can be byte-identical across two versions, so the bump is noise. - `gh release edit` on an untagged draft regenerates the URL slug and preserves the body. The changed link is normal, not corruption. Verify with a body diff. - Actions run logs expire (HTTP 410) faster than PR comments. Recover posted benchmark numbers with `gh api repos/<org>/<repo>/issues/<n>/comments --paginate`. - GitHub auto-redirects `/pull/` and `/issues/`, so either path links a number when you do not know its type. - `gh issue view <N> --repo <org>/<repo> --json title,body,comments` is the fastest authoritative issue-state check. Fetch issues sequentially. When the body truncates, fall back to the issues API plus its comments endpoint. - `gh api "search/issues?q=author:<user>+org:<org>+is:merged"` returns a count plus a search URL a skeptic can re-derive in a browser. - The `/events` endpoint returns events from followed users and watched repos, not only your own. Filter on actor equality. `PullRequestReviewEvent` may take its actor from the review author. - `author_association` is REST-only and per PR, not a `gh pr list` field. - `gh` keeps exactly one active account per host, globally. The credential helper always serves that token, so private-org repos fail with "Repository not found", not an auth error. - A manual `gh auth switch` is not durable. The global active account flips back on its own. - Run one command under another account with `GH_TOKEN=$(gh auth token -u <account>) gh <command>`, or `gh auth switch -u <account> -h github.com` when a switch is right. - Enterprise Managed User (EMU) tokens can read the API but not write: PR creation, replies, and review resolution fail with 403 through GraphQL and REST. Interactions must go out as the account that opened the PR. - Some repositories resolve only under a specific account. Switch accounts rather than retrying the failing slug. - Set private tap remotes to their SSH URL with `git remote set-url`. SSH authenticates with the key and ignores the active gh account, so `brew update` stops failing on org taps. - Verify OAuth App SSO with `gh api orgs/<org> --jq .login`: it returns the login when the token scope and the OAuth App SSO grant are both present, and 403 with "SAML enforcement" when the grant is missing, regardless of token validity. - Setup shortcuts: `gh auth setup-git` wires the gh credential helper for git HTTPS. `gh repo create --source=. --push` sets the remote, upstream, and pushes. When `gh pr create` hits a transient block, retry once with the body passed via a file. - Post a batched review with `gh api repos/<org>/<repo>/pulls/<n>/reviews --method POST --input <file>`. Every inline entry needs `path`, `line`, and `side:"RIGHT"`. One mismatched anchor fails the whole request. - Count Gerrit changes by owner domain: query the changes API with `owner:*<domain>+status:merged`, `n=500`, and `DETAILED_ACCOUNTS` for per-author and per-subsystem breakdowns with citable URLs. - The DevStats ranking call is a POST with api `DevActCnt`, a project, a range, the Contributions metric, and a GitHub id. The response holds parallel `rank`, `login`, and `number` arrays. An empty `login` array means unranked. ## How to apply 1. Gate scripts on `gh pr checks` exit codes. Treat pending on draft PRs as normal. 2. On a rate-limit 403 or 429, sleep until `resources.core.reset`, retry once, then abort before writing state. 3. Triage CI with per-job conclusions, then retry only failed jobs with `gh run rerun <run-id> --failed`. 4. Reach a second account for one command with `GH_TOKEN=$(gh auth token -u <account>) gh <command>`. ## Observations - [ci] Exit codes, not table text, are the reliable `gh pr checks` signal. #gh-cli (source: agent sessions, 2026-07) - [api] GraphQL and REST rate limits use separate buckets. #rate-limits (source: agent sessions, 2026-05) - [api] The runs API silently drops PR feedback when `exclude_pull_requests` has any value. #api (source: agent sessions, 2026-09) - [auth] A missing-repo error from `gh` can mean the wrong active account. #gh-cli (source: agent sessions, 2026-10) - [auth] EMU tokens can read but not write, through GraphQL and REST. #gh-cli (source: agent sessions, 2026-11) ## Relations - relates_to [[resources/documentation/GitHub - Pull Request Workflow]] (PR creation and account behavior) - relates_to [[resources/documentation/GitHub Actions - Semantics and Caching]] (check-runs and runs API caps) - relates_to [[resources/documentation/CI - Failure Triage and Silent Non-Runs]] (exit-code and per-job triage) - relates_to [[resources/documentation/SSH - Debugging Config, Agents, and Forwards]] (SSH remotes bypass the gh account) ## Sources Personal agent-session notes, January 2026 to November 2026.