| name | gh-cli |
| description | Patterns for invoking the GitHub CLI (gh v2.98.0+) from agents. Use when the task mentions GitHub, gh, pull requests/PRs, issues, releases, gists, Actions/workflow runs, forks, repo cloning, reviews, or you need exact gh commands. Covers pagination, repo targeting, search vs list, discussions, projects, rulesets, skills, and gh api fallback. |
GitHub CLI (gh) agent patterns
Authoritative patterns for driving the official gh CLI (v2.98.0) from agents,
based on cli/cli trunk. Prefer gh over raw curl
or gh api — gh handles auth, pagination, and JSON output automatically.
Security Advisory — escape-sequence injection (v2.97.0)
v2.97.0 fixed 4 escape-sequence injection vulnerabilities. These commands can
inject ANSI control sequences (cursor movement, screen clearing, clipboard
exfiltration) when output is rendered to a terminal:
| Affected command | Risk |
|---|
gh gist view | Untrusted gist content → terminal |
gh api | Untrusted API response → terminal |
gh pr diff | Untrusted PR content → terminal |
gh release download --output - | Untrusted release artifact → stdout/terminal |
gh codespace logs | Untrusted container output → terminal |
gh agent-task view / create | Untrusted task description/output → terminal |
Agent rules:
- Never pipe output to a terminal renderer — prefer
--json, use > file
for raw content; never gh release download --output - (stdout), always
--output <path>.
- For
gh repo read-file, binary content is auto-refused and ANSI is stripped
by default since v2.97; use --allow-escape-sequences only when you need raw
escapes and understand the risk.
- When fetching issues/PRs/comments from untrusted repos, prefer
--json over
human-readable output — JSON is not vulnerable to escape injection.
Other v2.96.0 / v2.97.0 fixes (advisory → impact → rule):
| Advisory | Impact | Agent rule |
|---|
| GHSA-cg6r-mpgc-h9mm | gh auth status (without --show-token) prints a token fragment for github_pat_* / ghs_* / ghu_* and the Actions GITHUB_TOKEN; classic gho_* / ghp_* are unaffected. | Upgrade ≥2.97.0; redact the token line before sharing output; never ship this output to CI logs. |
| GHSA-mm27-mwq9-fr5g | gh attestation verify --signer-repo/--signer-workflow interpolates the value into a regex without escaping metacharacters (. matches any char), so a lookalike name can satisfy a matcher intended for a trusted signer. | Only safe on ≥2.97.0; else use --repo/--owner (exact string match) or manually verify the SAN. |
| GHSA-4fjg-2h4q-fwg3 | Some REST request URLs built without escaping variable path segments → path traversal; a crafted name redirects gh to a different resource than intended. | Upgrade 2.97.0; be cautious with untrusted repo/input names. |
| GHSA-8cg3-r6g9-fpg2 | gh codespace jupyter opens the codespace-supplied URL unvalidated; a malicious codespace returns a vscode:// link → command execution on the host (variant of GHSA-p2h2-3vg9-4p87 / CVE-2024-52308). | Upgrade ≥2.96.0; only use trusted codespaces. |
Security Advisory — codespace port forwarding (v2.98.0)
GHSA-vfhh-p7hm-pxfh: gh codespace ports forwards container ports to the host
and, by default, binds them to all interfaces (0.0.0.0), not just localhost,
so a forwarded service is reachable from the local network. When forwarding a
port that serves sensitive or unauthenticated content, restrict exposure — do
not forward dev servers that hold secrets or unauthenticated endpoints.
Interactivity policy
gh does the right thing in non-TTY contexts: skips the pager, strips ANSI
color, and errors fast instead of prompting.
- Set
GH_PROMPT_DISABLED=1 to force gh to fail instead of prompting.
- A few commands still need explicit flags non-interactively:
gh pr merge
(--squash/--merge/--rebase), gh release create (--notes or
--generate-notes), gh pr create (--fill or explicit --title/--body).
- Exit codes:
0 success, 1 failure, 2 cancelled, 4 auth required.
| Variable | Effect |
|---|
GH_PROMPT_DISABLED=1 | Fail instead of prompting interactively |
GH_PAGER=cat | Disable pager (already auto in non-TTY) |
GH_NO_UPDATE_NOTIFIER=1 | Skip version check (saves a request) |
NO_COLOR=1 | Strip ANSI color from output |
GH_DEBUG=api | Log HTTP request/response for debugging |
GH_FORCE_TTY=1 | Force TTY output even when piped (debug color/rendering) |
GH_TELEMETRY=false | Disable telemetry (opt-out; default enabled since v2.91.0) |
Parsing JSON
--json field1,field2,... for structured output
--json with no field list prints available fields — use this first
--jq '<expr>' to filter without piping through jq
--template '<go-template>' for shaped text output. Note: -T/--template
collides with gh pr create/gh issue create body-template flag.
- Template helpers:
tablerow, tablerender, timeago, truncate, hyperlink,
pluck, join, color, autocolor, regexMatch, contains.
Pagination
List commands cap results silently:
gh pr list, gh issue list, gh search ...: use -L N (--limit N), default
30. No totalCount via --json — use gh api graphql for true totals.
gh api --paginate <path> concatenates each page's JSON. For [...] responses
that yields multiple arrays — add --slurp to wrap into one array.
gh api --cache 30m <path> caches responses to avoid repeat hits.
Repo targeting
gh infers the repo from cwd git remotes. Pass --repo OWNER/REPO (-R) to
override. Set GH_REPO=OWNER/REPO for session-wide default.
Search vs list
gh search issues|prs|code|repos|commits uses GitHub's search index with full
syntax. Each qualifier is its own bare token — do NOT quote them as one string:
gh search issues repo:cli/cli is:open author:monalisa works,
gh search issues "repo:cli/cli is:open" fails. Quote only multi-word free text.
gh issue list --search "..." / gh pr list --search "..." take one quoted
string, scoped to one repo.
- Bots author as GitHub Apps:
--author dependabot fails. Use --app dependabot
(on pr/issue list and search prs|issues).
- Exclude qualifiers with
-- stop-parser: gh search issues -- "error -label:bug".
- Full qualifier syntax (v2.79.0+):
author:, label:, milestone:, assignee:,
review:, status:, base:, head:, merged:, created:, updated:,
closed:, comments:, interactions:, reactions:.
gh search issues --search-type <lexical|semantic|hybrid> (v2.98+): semantic
relevance-ranked search, default lexical. semantic/hybrid are issue-only
(reject --include-prs, --sort/--order, --web), bound to one page
(separate 10/min bucket), github.com/ghe.com only — not single-tenant GHES.
Reviewing PRs (gh pr review vs inline comments)
gh pr review <n> submits only a top-level review — one verdict + one body:
gh pr review <n> --approve --body "LGTM"
gh pr review <n> --comment --body "notes…"
gh pr review <n> --request-changes --body "…"
gh pr review <n> --approve --body-file review.md
It has no flag for per-line comments — a common agent mistake. To attach
findings to specific lines, post one pending review via the REST API with a
comments[] array (new-side line numbers, inside changed hunks):
gh api repos/{owner}/{repo}/pulls/<n>/reviews --method POST \
-f event=COMMENT -f body="overall summary" \
-F 'comments[][path]=src/app.go' -F 'comments[][line]=42' \
-F 'comments[][body]=this needs a nil check' \
-F 'comments[][path]=src/app.go' -F 'comments[][line]=88' \
-F 'comments[][body]=off-by-one here'
event: APPROVE | REQUEST_CHANGES | COMMENT, or omit for a PENDING
draft. Never auto-APPROVE from an agent — leave the verdict to a human.
Place each finding at the tightest scope its location allows: line comment
(inside a changed hunk), file-level comment (file in the diff, line outside a
hunk), or review body (file not in the diff). Line numbers must be the new-side
line inside a changed hunk.
gh pr diff escape sequences (v2.97.0)
In non-TTY output, gh pr diff REJECTS (errors, does not strip) when the diff
contains escape sequences. To write a raw diff to a file, pass
--allow-escape-sequences explicitly. -e <pattern> is the short flag for
--exclude (matches path AND basename, e.g. gh pr diff -e '*.generated.*').
gh pr checks
gh pr checks [--watch] [--fail-fast] [-i/--interval ] [--required]
[--json ]
- --watch: poll until checks finish; --interval sets poll cadence.
- --fail-fast: exit on first failure.
- --required: only show required checks.
- JSON
bucket field groups checks by state (pending/pass/fail).
- Exit code 8 = checks failed (distinct from 1 = command error).
- --json and --watch are mutually exclusive.
gh api — the universal fallback
When no porcelain command covers what you need:
GraphQL (preferred for complex queries)
gh api graphql -f query='query { viewer { login } }'
gh api graphql -F owner='cli' -F name='cli' -f query='query($name:String!,$owner:String!){repository(owner:$owner,name:$name){releases(last:3){nodes{tagName}}}}'
gh api graphql --paginate -f query='query($endCursor:String){search(query:"is:pr is:merged",type:ISSUE,first:100,after:$endCursor){nodes{...on PullRequest{number title}}pageInfo{hasNextPage endCursor}}}'
REST
gh api repos/{owner}/{repo}/releases
gh api repos/{owner}/{repo}/issues/123/comments -f body='Hello'
gh api --paginate --slurp repos/{owner}/{repo}/issues --jq 'map(.number)'
gh api --cache 30m repos/{owner}/{repo}
{owner}/{repo} placeholders auto-fill from detected remotes.
-f key=value sends strings; -F key=value parses numbers/booleans/@file.
Other flags: -i/--input <file> reads the request body from a file;
--verbose / --silent control logging; -p/--preview <name> opts into an
OpenAPI preview; --allow-escape-sequences preserves raw escapes. There is
no --preauthorize flag — do not invent one.
Authentication
gh auth status --json — active host, user, auth source
GH_TOKEN / GITHUB_TOKEN env vars for non-interactive/CI use
GH_ENTERPRISE_TOKEN for GHES, GH_HOST for enterprise instances
@me resolves to the authenticated user (--assignee @me, --author @me)
- Never paste tokens on the command line; use
--with-token < file or env vars
Rate limits
gho_* OAuth tokens (from gh auth login) get 5,000 GraphQL points/hr.
GITHUB_TOKEN (Actions) is capped at 1,000 requests/hr/repo.
- Rely on response headers
x-ratelimit-remaining / x-ratelimit-reset, not
polling /rate_limit — cheaper and always accurate.
- On
429/403, read retry-after and back off; don't retry blindly.
- Secondary limits: match
\bsecondary rate\b in the response body — retry-after
and gh-limited-by headers are often absent (github.com obfuscation). Wait
≥60s, then exponential backoff.
Agent Skills (gh skill) — v2.94.0+
gh skill search [--owner ] # no --agent flag on search
gh skill list --json skillName,description,installed
gh skill preview
gh skill install owner/repo --agent opencode # required non-interactively
gh skill install owner/repo --agent opencode --pin v1.2
gh skill install owner/repo --agent opencode --upstream # pull from upstream remote
gh skill install cli/cli gh --scope user # self-install gh's own skill
gh skill update [--all] [--dry-run]
gh skill publish [
] [--dry-run] [--tag ] [--fix] # validate skills + cut a release
Default scope is project; default agent is github-copilot — always pass
--agent opencode (installs to ~/.config/opencode/skills/). Supported --agent
hosts: github-copilot, opencode, Devin, Grok, Cursor, antigravity-cli
(alias antigravity2.0), Windsurf (legacy), and more as the ecosystem adds
them. --scope user|project picks the install location.
Agentic CI (gh aw) — GitHub's agentic CI framework (extension)
gh aw init # scaffold the gh-aw workflow into the repo
gh aw compile # compile the agent workflow (main flow)
gh aw run / logs / audit / status / checks / fix / upgrade / deploy
Still an extension (gh extension install github/gh-aw), NOT merged into the
main gh command. WARNING: the billing bug affecting versions 0.68.4–0.71.3
is retired — upgrade past 0.71.3 before using.
Release verification (gh release verify) — v2.75.0+
Verify release artifact attestations (Sigstore supply-chain). No auth needed for
public repos.
gh release verify -R cli/cli
gh release verify v2.96.0 -R cli/cli
gh release verify-asset cli.zip -R cli/cli
Note: Releases created with v2.93.0+ are immutable — JSON output includes an
isImmutable field. Use gh release download <tag> (no auth for public repos,
v2.96.0+) to fetch artifacts.
Attachments (--attach)
--attach <path> works on gh issue/gh pr create/edit/comment, repeatable
up to 50 per item. Formats: png/jpg/gif/webp/svg (images) and mp4/mov/webm
(video). Image alt text follows a # suffix: --attach 'shot.png#crash screenshot'.
gh issue create --title "crash" --attach shot.png#crash --attach repro.mp4
Cold subcommands (one-liners)
gh project — V2 projects: item-add/item-list/item-edit/field-list; by-name field editing --field "Status" --value "Done" (v2.97.0+).
gh ruleset — list/view/check -b <branch> (compliance).
gh cache — list/delete; delete --succeed-on-no-caches exits 0 when none match.
gh repo read-file <path>/read-dir [<path>] — read repo contents without cloning (--ref, --output, --clobber); piped output is raw bytes, binary auto-refused.
gh discussion — list/view/create/comment/edit (v2.94.0 preview).
gh issue create/edit — --type Bug|Feature|Task, --parent, --blocked-by, --blocking; edit adds --add-sub-issue/--add-blocked-by/--add-blocking and --remove-* (types GHES 3.17+, relationships 3.19+).
gh gist — create/list/view --raw/edit/delete/clone.
gh secret / gh variable — set/list/remove; scoped repo/org/env. gh codespace — list/create/stop/delete/logs/ssh/ports.
gh config — set/get/list/clear-cache (editor, git_protocol, prompt). gh extension — install/list/upgrade/remove// (no auth since v2.90.0). — //; pipes to editors.
Quick reference
gh issue create --type Bug --title "..." --body "..."
gh issue list --type Bug --assignee @me -L 20 --json number,title,state,issueType
gh issue close <n> --duplicate-of <n>
gh pr list --state open --label bug -L 20 --json number,title,state,headRefName
gh pr view <n> --json state,mergeable,reviewDecision,statusCheckRollup
gh pr create --fill --base main
gh pr diff <n> --exclude '*.generated.*'
gh pr merge <n> --squash --delete-branch
gh pr checks <n> --watch --required
gh run list --workflow ci.yml --branch main --limit 20
gh run view <id> --log-failed
gh run watch <id> --exit-status --compact
gh release create v1.2.0 --generate-notes
gh release download <tag>
gh release verify -R owner/repo
gh search prs --author @me --state open --label bug --repo OWNER/REPO
gh search issues --repo OWNER/REPO --search "error in:title"
gh skill search <query>
gh skill install owner/repo --agent opencode
gh status
gh config set git_protocol ssh
gh auth status --json