| name | gh |
| description | Use when the user mentions `gh`, `gh api`, the GitHub CLI, the GitHub API, or wants to view, query, search, or change GitHub. Covers querying the GitHub API, raw API calls, PRs/pull requests, review comment replies, issues, workflows/Actions/CI, releases, repos, notifications, status, and any task that views or queries GitHub data. |
| user-invocable | true |
GitHub CLI (gh) Reference
Fence policy
Coding agents run fenced. Fence permits the everyday mutations:
git push, gh pr comment, gh-review-reply, gh issue create,
gh issue edit, gh run rerun, gh run cancel, gh pr update-branch,
and gh pr review --approve. Invoking a command that names a mutation
is the consent for that mutation, so run it rather than asking again.
Fence denies raw gh api, gh pr merge, gh workflow run, the
gh release mutations, gh repo create and gh repo edit, gh config,
gh secret, gh variable, and the other destructive namespaces. Output
those for the operator to run in an unfenced shell. Raw reads go through
gh-api-safe.
Body text policy
Every command below that carries a --body or --body-file publishes
under the user's name. Load the contribution-voice skill and follow it
before writing that text. It governs the structure: length, layout,
sign-offs, and the cut pass.
This covers gh pr create, gh pr comment, gh pr review,
gh issue create, gh issue comment, and gh-review-reply.
Prefer the dedicated commands where one fits, because each already loads
the skill: make-pr, post-comment, post-issue, and
post-code-review. Reach for a bare gh call only when no command
covers the case.
Pull Requests
gh pr list
gh pr list --state merged --limit 10
gh pr list --json number,title,headRefName,statusCheckRollup
gh pr view 123
gh pr view 123 --comments
gh pr view 123 --json state,mergeable,mergeStateStatus | jq
gh pr create --fill
gh pr create --title "feat: add X" --body "..." --draft
gh pr create --base main --reviewer alice,bob --label "needs-review"
gh pr merge 123 --squash --delete-branch
gh pr merge 123 --auto --squash
gh pr review 123 --approve --body-file review.md
gh pr review 123 --comment --body "LGTM"
gh pr review 123 --request-changes --body "Please fix X"
gh pr comment 123 --body "LGTM"
gh pr comment 123 --edit-last --body "Updated: LGTM"
gh pr checks 123
gh checks 123 --watch
gh edit 123 --add-label --add-reviewer charlie
gh edit 123 --base develop --title
gh checkout 123
gh diff 123
gh revert 123 --title
gh ready 123
gh update-branch 123
Issues
gh issue list
gh issue list --assignee @me --state open
gh issue list --label "bug" --json number,title,state
gh issue view 456
gh issue view 456 --comments
gh issue create --title "Bug: X fails" --body "Steps..." --label "bug" --assignee @me
gh issue edit 456 --add-label "priority" --milestone "v2.0"
gh issue close 456
gh issue reopen 456
gh issue comment 456 --body "Fixed in #123"
gh issue develop 456 --name "fix/issue-456"
CI / Actions
gh run list
gh run list --branch main --status failure --limit 5
gh run list --workflow build.yml --json name,status,conclusion,headBranch
gh run view 12345678
gh run view 12345678 --verbose
gh run view 12345678 --log-failed
gh run view 12345678 --log
gh run view 12345678 --exit-status
gh run view 12345678 --json jobs --jq '.jobs[] | {name, databaseId}'
gh run view 12345678 --job 98765432
gh run watch 12345678
gh run rerun 12345678
gh run rerun 12345678 --failed
gh run rerun 12345678 --debug
gh run cancel 12345678
gh workflow run deploy.yml --ref main
gh workflow run deploy.yml -f env=staging -f version=1.2.3
gh workflow list
gh workflow view build.yml
Releases
gh release create v1.2.3 --generate-notes
gh release create v1.2.3 --title "v1.2.3" --notes "Fixes #123" dist/*.tar.gz
gh release create v1.2.3 --draft --prerelease
gh release create v1.2.3 --notes-from-tag
gh release upload v1.2.3 dist/binary.tar.gz
gh release list
gh release view v1.2.3
gh release download v1.2.3
Repository
gh repo view
gh repo view owner/repo --json name,description,defaultBranchRef,isPrivate
gh repo clone owner/repo
gh repo fork owner/repo --clone
gh repo create my-project --private --clone
gh repo create my-project --public --source=. --push
gh repo edit --default-branch main --enable-auto-merge
gh repo edit --description "New description" --homepage "https://example.com"
gh pr list -R owner/other-repo
Search
gh search repos "nix config" --language nix --stars ">100" --sort stars
gh search issues "memory leak" --repo owner/repo --state open
gh search prs "fix authentication" --author alice --merged
gh search prs --repo owner/repo --checks failure --state open
gh search code "sops.placeholder" --repo owner/repo --language nix
Raw API
Default to a dedicated gh subcommand. Use gh-api-safe only when no
subcommand fits. Raw gh api is denied under Fence. Reserve it for
mutations with no subcommand and for @file field input, and output the
command for the operator to run in an unfenced shell.
| Situation | Use |
|---|
| Read-only REST fetch | gh-api-safe <path> |
| GraphQL read (queries only) | gh-api-safe graphql -f query='…' |
| Dedicated subcommand exists | that subcommand (gh pr edit, gh issue edit, ...) |
| Reply inside a review comment thread | gh-review-reply <review-comment-url> |
| Other mutation (POST/PATCH/PUT/DELETE) | gh api -X ... in unfenced shell |
Field input from file (-F x=@file) | raw gh api in unfenced shell |
gh-api-safe wraps gh api, enforces a read-only allow-list with a
defence-in-depth deny-list on the REST path, blocks
-X/--method/-f/-F/--field/--raw-field/--input (except query= value under graphql, where @file is still rejected), and runs a
best-effort GraphQL heuristic that rejects any query whose body contains
a surviving mutation or subscription keyword after comments and
string literals have been stripped. The heuristic is not a real GraphQL
parser; aliased mutations are out of scope and @file queries are
rejected outright. Policy rejections exit 64 with a single-line reason
on stderr; on rejection, switch to the matching dedicated subcommand or
escalate to an unfenced shell rather than retrying the same call. Run
gh-api-safe --help for the full policy summary.
Placeholders {owner}, {repo}, {branch} are replaced from current
git context. Default method is GET.
gh-api-safe repos/{owner}/{repo}/actions/runs \
--jq '.workflow_runs[:5] | .[] | {name, conclusion, html_url}'
gh-api-safe repos/{owner}/{repo}/issues --paginate --jq '.[].title'
gh-api-safe graphql -f query='{ viewer { login } }'
gh-api-safe graphql -f query='mutation { addStar(input: {starrableId: "X"}) { starrable { id } } }'
gh-api-safe graphql -f query=@query.graphql
gh-api-safe notifications --jq '.[] | {reason, subject: .subject.title}'
gh-review-reply is the only write path through the raw API surface
allowed under Fence; every other permitted mutation runs as a dedicated
gh subcommand. It takes the review comment URL and a body file,
nothing else. Owner, repository, pull request number, and comment id are
parsed out of the URL, and one endpoint is built from them,
POST repos/{owner}/{repo}/pulls/{n}/comments/{id}/replies. The reply
body is read from a file so quotes, backticks, and newlines survive
verbatim. {owner} placeholders are not expanded; pass the real URL.
Any other flag (-X, -f, -F, --input, or a glued
--body-file=PATH) exits 64 with a single-line reason on stderr.
The URL must begin with https://github.com/ and its path must be
<owner>/<repo>/pull/<number>, with an optional trailing segment such as
/files. Both anchor forms work: #discussion_r<id> from the
conversation tab and #r<id> from the files tab. An
#issuecomment-<id> fragment names a top-level comment, not a review
comment; use gh pr comment for that.
gh-api-safe repos/{owner}/{repo}/pulls/123/comments \
--jq '.[] | {id, path, user: .user.login, url: .html_url}'
gh-review-reply https://github.com/owner/repo/pull/123#discussion_r2109876543 \
--body-file reply.md
See home-manager/_mixins/agentic/fence/default.nix for the
authoritative command.allow / command.deny lists,
home-manager/_mixins/agentic/fence/README.md for the policy overview,
and home-manager/_mixins/development/github/gh-api-safe.sh for the
wrapper source.
Unsafe: requires unfenced shell
⚠️ The commands below mutate GitHub state. They use gh api directly
with -X / -F / --input and are rejected by gh-api-safe. They must
only be run in an unfenced shell with explicit operator consent. Prefer
the dedicated gh subcommands (gh issue edit, gh pr edit, etc.)
wherever they exist.
gh api repos/{owner}/{repo}/issues/456 -X PATCH -F state=closed
gh api repos/{owner}/{repo}/labels -F name="triage" -F color="e4e669"
gh api repos/{owner}/{repo}/issues -F title="Bug" -F body=@issue.md
JSON Output Pattern
Most commands accept --json fields with optional --jq expression:
gh pr list --json number,title,state,headRefName
gh run list --json name,status,conclusion,headBranch,createdAt
gh pr list --json number,title,statusCheckRollup \
--jq '.[] | select(.statusCheckRollup | any(.state == "FAILURE")) | .number'
gh pr view 123 --json mergeable,mergeStateStatus
gh issue list --json number,title,labels | jq '.[] | select(.labels | any(.name == "bug"))'
Status & Auth
gh status
gh auth status
gh auth token