# Gh

> GitHub CLI (gh) Reference

- Skill: `wimpysworld/gh` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add wimpysworld/gh`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wimpysworld/gh/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: wimpysworld (https://skillmd.com/u/wimpysworld)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wimpysworld/gh

---


# 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 issue comment`, `gh issue develop`, `gh project item-add`,
`gh project item-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. Apply `contribution-voice` before writing that text.
Read it first unless its complete, current instructions are already in this context.
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

```bash
# List
gh pr list
gh pr list --state merged --limit 10
gh pr list --json number,title,headRefName,statusCheckRollup

# View
gh pr view 123
gh pr view 123 --comments
gh pr view 123 --json state,mergeable,mergeStateStatus | jq

# Create
gh pr create --fill                                             # title/body from commits
gh pr create --title "feat: add X" --body "..." --draft
gh pr create --base main --reviewer alice,bob --label "needs-review"

# Merge — `gh pr merge` is denied under Fence. Output the command for
# the operator to run in an unfenced shell; do not execute it.
gh pr merge 123 --squash --delete-branch
gh pr merge 123 --auto --squash                                 # merge once CI passes

# Review & comment
# --approve is allowed under Fence. GitHub refuses approval of your own
# pull request, so that case fails on GitHub's side. Prefer
# `post-code-review`, which drafts the review and then posts it.
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` posts at the top level. To answer inside a review
# comment thread use `gh-review-reply` (see Raw API).
gh pr comment 123 --body "LGTM"
gh pr comment 123 --edit-last --body "Updated: LGTM"

# CI status
gh pr checks 123
gh pr checks 123 --watch                                        # stream until complete

# Edit
gh pr edit 123 --add-label "bug" --add-reviewer charlie
gh pr edit 123 --base develop --title "Updated title"

# Other
gh pr checkout 123
gh pr diff 123
gh pr revert 123 --title "revert: undo X"
gh pr ready 123                                                 # mark draft as ready
gh pr update-branch 123                                         # merge base into head
```

## Issues

```bash
# List
gh issue list
gh issue list --assignee @me --state open
gh issue list --label "bug" --json number,title,state

# View
gh issue view 456
gh issue view 456 --comments

# Create
gh issue create --title "Bug: X fails" --body "Steps..." --label "bug" --assignee @me

# Manage
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"                    # create linked branch
```

## CI / Actions

```bash
# List runs
gh run list
gh run list --branch main --status failure --limit 5
gh run list --workflow build.yml --json name,status,conclusion,headBranch

# View a run
gh run view 12345678
gh run view 12345678 --verbose                                  # all job steps
gh run view 12345678 --log-failed                               # logs for failed steps only
gh run view 12345678 --log                                      # full log
gh run view 12345678 --exit-status                             # non-zero exit if failed (scripts)

# Get job IDs (required for --job flag)
gh run view 12345678 --json jobs --jq '.jobs[] | {name, databaseId}'
gh run view 12345678 --job 98765432

# Watch live
gh run watch 12345678

# Rerun
gh run rerun 12345678
gh run rerun 12345678 --failed                                  # only failed jobs
gh run rerun 12345678 --debug                                   # with debug logging

# Cancel
gh run cancel 12345678

# Trigger workflow_dispatch — `gh workflow run`, `enable`, and `disable`
# are denied under Fence. Output the command for operator consent.
gh workflow run deploy.yml --ref main
gh workflow run deploy.yml -f env=staging -f version=1.2.3

# List workflows
gh workflow list
gh workflow view build.yml
```

## Releases

```bash
# Create / upload — the `gh release` family is denied under Fence except
# for `list`, `view`, and `download`. Mutations below require an unfenced
# shell with explicit operator consent.
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                      # use annotated tag message
gh release upload v1.2.3 dist/binary.tar.gz

# List / view / download (allow-listed reads)
gh release list
gh release view v1.2.3
gh release download v1.2.3
```

## Repository

```bash
# View
gh repo view
gh repo view owner/repo --json name,description,defaultBranchRef,isPrivate

# Clone / fork
gh repo clone owner/repo
gh repo fork owner/repo --clone

# Create / edit — denied under Fence (`gh repo create`, `gh repo edit`,
# `rename`, `set-default`, `sync`, `archive`, `unarchive`, `deploy-key`).
# Output for operator-run unfenced shell.
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"

# Cross-repo flag (works on most commands)
gh pr list -R owner/other-repo
```

## Search

```bash
# Repositories
gh search repos "nix config" --language nix --stars ">100" --sort stars

# Issues and PRs across GitHub
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   # failing CI

# Code (legacy engine; for regex use `gh-api-safe 'search/code?q=...'`)
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.

```bash
# GET with jq
gh-api-safe repos/{owner}/{repo}/actions/runs \
  --jq '.workflow_runs[:5] | .[] | {name, conclusion, html_url}'

# Paginate all results
gh-api-safe repos/{owner}/{repo}/issues --paginate --jq '.[].title'

# GraphQL (heuristic-screened; mutations and subscriptions are rejected)
gh-api-safe graphql -f query='{ viewer { login } }'

# Rejected: surviving `mutation` keyword (exit 64 on stderr)
gh-api-safe graphql -f query='mutation { addStar(input: {starrableId: "X"}) { starrable { id } } }'

# Out of scope: `@file` query bodies are rejected outright (exit 64)
gh-api-safe graphql -f query=@query.graphql

# Notifications (read only; PUT mark-as-read is blocked by the wrapper)
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.

```bash
# Copy the review comment URL from the thread, or find the comment id
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.

```bash
# PATCH / POST
gh api repos/{owner}/{repo}/issues/456 -X PATCH -F state=closed
gh api repos/{owner}/{repo}/labels -F name="triage" -F color="e4e669"

# Typed fields (-F): true/false/null/integers become JSON types; @file reads file
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`:

```bash
# Named fields only
gh pr list --json number,title,state,headRefName
gh run list --json name,status,conclusion,headBranch,createdAt

# Filter inline
gh pr list --json number,title,statusCheckRollup \
  --jq '.[] | select(.statusCheckRollup | any(.state == "FAILURE")) | .number'

# Check mergeability
gh pr view 123 --json mergeable,mergeStateStatus

# Combine with jq outside gh for complex transforms
gh issue list --json number,title,labels | jq '.[] | select(.labels | any(.name == "bug"))'
```

## Status & Auth

```bash
gh status                  # cross-repo overview: assigned PRs, review requests, mentions
gh auth status             # active account, token scopes, expiry
gh auth token              # allowed under Fence; never echo the value
# `gh auth setup-git` and `gh auth login --with-token` stay denied.
```

