gh ยท diff

git:20260919.abe4774 to git:20261020.10da5a6

122 added, 5 removed. Audit A to A.

---
name: gh
description: Patterns for invoking the GitHub CLI (gh) from agents. Covers structured output, pagination, repo targeting, search vs list, gh api fallback.
---
# Reference
## Interactivity policy
`gh` already does the right thing in non-TTY contexts: it skips the pager,
strips ANSI color, and errors out fast with a helpful message instead of
prompting (e.g. `must provide --title and --body when not running interactively`).
You don't need to defensively set `GH_PAGER` or pass `--no-pager` (no such
flag exists).
## Parsing JSON
Human output from `gh` is column-formatted. If you want structured data:
- Add `--json field1,field2,...` for structured output.
+ - For `gh issue view` and `gh pr view`, use `--json ...,comments` to include comments in structured data; use `--comments` only with the human-readable view.
- Run a command with `--json` and **no field list** to print the full set of
available fields, then pick what you need.
- Use `--jq '<expr>'` for filtering without piping through a separate `jq`.
- Use `--template '<go-template>'` (alongside `--json`) when you want shaped
text output. Note that `--template`/`-T` collides with a body-template flag
on a few commands (e.g. `gh pr create -T`, `gh issue create -T`); always
check `--help` before assuming which one you're hitting.
## Pagination and silent truncation
List commands cap results.
- `gh issue list`, `gh pr list`, `gh search ...`: pass `-L N` (`--limit N`).
The default is usually 30.
- `gh issue list` / `gh pr list` do not expose aggregate totals like
`totalCount` via `--json`. If you need a true total, use `gh api graphql`
to query `totalCount`; otherwise, treat `-L` as the cap for the current call.
- For raw API calls use `gh api --paginate <path>`. Combine with
`--jq` and (optionally) `--slurp` to assemble one array.
## Repo targeting
`gh` infers the repo from the cwd's git remotes.
Pass `--repo OWNER/REPO` (`-R`) to override the resolved CWD repo.
## Search vs list
- `gh search issues|prs|code|repos|commits|users` uses GitHub's search
index and accepts the full search syntax (`is:open`, `author:`,
`label:`, `repo:owner/name`, `in:title`, ...). Pass each qualifier as
its own bare token, not as one quoted string:
`gh search issues repo:cli/cli is:open author:monalisa` works, but
`gh search issues "repo:cli/cli is:open"` is treated as a single keyword (parsed as `repo:"cli/cli is:open"`)
and fails with `Invalid search query`. Quote only multi-word free text
(`gh search issues "broken feature"`). Most qualifiers also have a
dedicated flag (`--repo`, `--author`, `--label`, ...). Prefer search for
anything cross-repo or filtered by author/label.
- `gh issue list --search "..."` and `gh pr list --search "..."` take the
query as one quoted string (it is a flag value) and are scoped to one repo.
- Bots author as GitHub Apps, so `--author dependabot` matches nothing. Use
`--app dependabot` (on `pr`/`issue list` and `search prs|issues`; expands
to `author:app/<slug>`) or `--author "dependabot[bot]"`.
- `gh search issues` also takes `--search-type <lexical|semantic|hybrid>`
(github.com/GHEC only, issues only): use `semantic` when the user describes a
problem in natural language rather than exact terms, and `hybrid` to blend
keyword and semantic ranking; `lexical` (default) is exact matching.
## Issue types, sub-issues, and relationships
Newer `gh issue` subcommands model issue types, sub-issue hierarchy, and
blocked-by/blocking relationships.
- `gh issue create`: `--type <name>`, `--parent <number|url>` (creates the
new issue as a sub-issue), `--blocked-by <number|url,...>`, `--blocking <number|url,...>`.
- `gh issue edit` (edits one or more issues in the same repo, e.g.
`gh issue edit 23 34`): `--type <name>` / `--remove-type`,
`--parent <n|url>` / `--remove-parent`,
`--add-sub-issue <n,n>` / `--remove-sub-issue <n,n>`,
`--add-blocked-by <n,n>` / `--remove-blocked-by <n,n>`,
`--add-blocking <n,n>` / `--remove-blocking <n,n>`. Relationship and parent
refs are issue numbers or URLs; a URL may point to another repo on the same
host, but a different host is rejected. `--add-sub-issue` cannot be used
when editing more than one issue.
- `gh issue list --type <name>` filters by issue type.
- `gh issue view` and `gh issue list` accept these as `--json` fields (prefer
them over scraping the default text output): `issueType`, `parent`,
`subIssues`, `subIssuesSummary`, `blockedBy`, `blocking`. `subIssues`,
`blockedBy`, and `blocking` are objects shaped
`{"nodes": [...], "totalCount": N}` (not flat arrays), and `nodes` is capped
(`subIssues` at 100, `blockedBy`/`blocking` at 50), so compare the node count
against `totalCount` to detect truncation.
- GHES: issue types and sub-issues need 3.17+; blocked-by/blocking
relationships need 3.19+.
## Attaching images and videos
`--attach <path>` is available on `gh issue create`, `gh issue edit`,
- `gh issue comment`, `gh pr create`, `gh pr edit`, and `gh pr comment`.
+ `gh issue comment`, `gh pr create`, `gh pr edit`, `gh pr comment`,
+ `gh issue artifact create`, and `gh issue artifact edit`.
- Repeat `--attach` to upload multiple files:
`gh issue comment 12 --attach ./before.png --attach ./after.png`.
- Each command invocation accepts at most 50 `--attach` values total across
images and videos.
- Supported files are `png`, `jpg`, `jpeg`, `gif`, `webp`, `svg`, `mp4`,
`mov`, and `webm`.
- For an image, append alt text to the path after `#`. Quote the value so the
shell does not treat `#` as a comment:
`gh pr create --attach './login.png#The login error state'`. Without alt
text, the filename is used.
- `--attach` paths and local Markdown destinations may be absolute or relative
- to the directory where `gh` runs.
+ to the directory where `gh` runs. When the body comes from a file with
+ `--body-file`, or from a file argument to `gh issue artifact create`,
+ relative destinations resolve against that file's directory first, then the
+ directory where `gh` runs.
- If the body references an attached path, `gh` rewrites that Markdown
reference to the uploaded URL. The reference keeps its existing alt text.
Otherwise, `gh` appends the attachment to the body. For example:
`gh pr edit 23 --body '![error](./login.png)' --attach ./login.png`.
- Videos cannot take alt text. A standalone `![recording](./repro.mp4)` becomes
a bare player URL, while an inline video image becomes a link. A
reference-style video image such as `![recording][clip]` with
`[clip]: ./repro.mp4` is rejected; use a reference-style link instead.
- `gh issue create` and `gh pr create`: `--attach` cannot be used with
`--web`. `gh pr create --attach` also cannot be used with `--dry-run`.
- `gh issue edit`: `--attach` can edit only one issue at a time.
- `gh issue comment` and `gh pr comment`: `--attach` cannot be used with
`--web` or `--delete-last`. It works alone, with `--edit-last`, or with one
of `--body`, `--body-file`, or `--editor`.
+ - `gh issue artifact create` and `gh issue artifact edit`: `--attach` works
+ only on Markdown documents, not links or HTML documents. With several
+ Markdown documents, `create` uploads each file once and points every
+ document that references it at the upload, and a file that no document
+ references is an error before anything uploads. `edit` with only
+ `--attach` appends the files to the artifact's current content.
- Uploads require GitHub.com or a GHE.com tenant, an OAuth token, classic PAT,
or fine-grained PAT, and `WRITE`, `MAINTAIN`, or `ADMIN` repository
- permission. GitHub Enterprise Server and GitHub App tokens are unsupported.
+ permission. GitHub Enterprise Server and most GitHub App tokens are
+ unsupported.
- Uploads stop at the first failure. If earlier files uploaded, `gh` still
- writes those attachments and exits non-zero. Create and edit commands also
- print the issue or pull request URL.
+ writes those attachments and exits non-zero. `gh issue create`,
+ `gh issue edit`, `gh pr create`, and `gh pr edit` also print the issue or
+ pull request URL. `gh issue artifact create` and `edit` still save an
+ artifact that is missing some of its files if any of them uploaded, with
+ the upload error as the reason on its `created` or `updated` line. If none
+ of them uploaded, the artifact isn't saved and gets a `failed` line.
## Discussions (`gh discussion`)
Preview command set, subject to change. Subcommands:
- `gh discussion list [--state open|closed|all] [--category <name>] [--author <handle>] [--label <name>,...] [--answered] [--search <query>] [--sort created|updated] [--order asc|desc] [--limit N] [--after <cursor>] [--json <fields>] [--web]`
lists a repo's discussions. `--state` defaults to open, `--sort` to updated,
`--order` to desc. `--answered` is tri-state (`--answered=false` for
unanswered) for Q&A categories.
- `gh discussion view {<number>|<url>|<comment-id>|<comment-url>} [--comments] [--order oldest|newest] [--limit N] [--after <cursor>] [--json <fields>] [--web]`
shows a discussion's body; add `--comments` for its comments, or pass a
comment ID/URL as the argument to list that comment's replies (no
`--replies` flag; `--comments` is rejected with a comment argument).
`--order` (default newest), `--limit`, and `--after` apply only to comment
and reply listings.
- `gh discussion create [--title <t>] [--body <b> | --body-file <path>] [--category <name>] [--label <name>,...]`
creates a discussion. `--title`, a body (`--body` or `--body-file`), and
`--category` are required non-interactively; omitting any will prompt on a
terminal.
- `gh discussion edit {<number>|<url>} [--title <t>] [--body <b>] [--body-file <path>] [--category <name>] [--add-label <name>,...] [--remove-label <name>,...]`
edits title, body, category, or labels.
- `gh discussion comment {<number>|<discussion-url>|<comment-id>|<comment-url>} [--body <b>] [--body-file <path>] [--edit] [--delete] [--yes]`
adds a top-level comment (when given a discussion) or a reply (when given a
comment); `--edit` or `--delete` updates or removes a comment/reply and
needs a comment ID or URL. `--yes` skips the `--delete` confirmation.
- `--json`/`--jq`/`--template` are available on `list` and `view` only;
`create` and `edit` print the discussion URL. `comment` prints the discussion comment (or reply) URL.
+
+ ## Issue artifacts (`gh issue artifact`)
+
+ Preview command set, subject to change. Issue artifacts are Markdown documents
+ (type `generic` or `plan`), HTML documents (type `embedded_html`), and links
+ (type `link`) attached to an issue. Each has a number on its issue, and
+ commands address artifacts by that number, since names aren't unique. They
+ are not GitHub Actions artifacts, which `gh run download` handles.
+ Subcommands:
+
+ - `gh issue artifact list {<issue-number>|<issue-url>} [--type generic|plan|link|embedded_html] [--limit N] [--json <fields>]`
+ (alias `ls`) lists artifacts in number order. There is no default limit:
+ every artifact is listed unless `--limit` is set. `--type generic` and
+ `--type plan` both list every Markdown document, and each keeps its stored
+ type. Piped output is tab-separated number, name, type, and RFC 3339
+ update time. `--json` fields include `body`, so one call can return every
+ artifact's content.
+ - `gh issue artifact view {<issue> <artifact-number> | <artifact-url>} [--version N] [--json <fields>] [--web]`
+ shows one artifact and its edit history. In a terminal, it ends with the
+ artifact's URL. Piped output is `name:`, `number:`, `version:`, `type:`,
+ `creator:`, `created:`, `updated-by:`, `updated:`, and `url:` lines, in
+ that order, then `--` and the raw body. `--version N` shows that version
+ from the edit history. `--version`, `--json`, and `--web` are mutually
+ exclusive. `--json versions` returns the edit history. `--web` opens a
+ document's page on GitHub, or a link's URL.
+ - `gh issue artifact create {<issue-number>|<issue-url>} [<file>[#<name>] | <url>]... [--body <text> | --body-file <path>] [--name <name>] [--type generic|plan|embedded_html] [--attach <path>]...`
+ (alias `new`) creates one artifact per file or URL, in argument
+ order. A text file becomes a document named after the file, or after the
+ text following `#`:
+ `gh issue artifact create 142 'docs/signin-plan.md#Sign-in plan'`. A
+ `.html` or `.htm` file becomes an HTML document. An
+ `http(s)` URL becomes a link named after its host, and a `.url` Internet
+ Shortcut file becomes a link to its URL. Names can't contain some
+ characters, such as parentheses. `gh` drops them from names it takes from
+ file names (`plan (1).md` becomes `plan 1.md`), but sends a name given with
+ `#` or `--name` as typed. Instead of file or URL arguments, `--body <text>`
+ creates one document and `--body-file <path>` creates one artifact from a
+ file. Neither can be combined with file or URL arguments. `--name` names a
+ single artifact. It's required with `--body` or `--body-file -`, and can't
+ be combined with a `#<name>` suffix. Documents are `generic`, and HTML
+ documents `embedded_html`. `--type` sets the type of every document
+ instead, whatever the file extension, so `--type embedded_html` makes
+ `--body` or `--body-file -` an HTML document. A plan currently behaves the
+ same as a generic document. `--type` is refused if any artifact is a link.
+ - `gh issue artifact edit {<issue> <artifact-number> | <artifact-url>} [--name <name>] [--body <text> | --body-file <path>] [--attach <path>]... [--restore-version N]`
+ renames an artifact or replaces its content, saving a new version. At least
+ one of `--name`, `--body`, `--body-file`, `--attach`, or `--restore-version`
+ is required, and a `--body-file` never renames the artifact. The type never
+ changes: a link's new content must be an `http(s)` URL or a `.url` file, and
+ a document can't take a `.url` file. `--restore-version N` saves the name
+ and content of version N from the edit history as a new version. It works
+ for any version `view` lists, and can't be combined with `--name`,
+ `--body`, `--body-file`, or `--attach`. If the artifact already matches
+ that version, nothing is saved and `gh` exits 0, with a `skipped` line when
+ piped.
+ - `gh issue artifact delete {<issue> <artifact-number>... | <artifact-url>...} [--yes]`
+ deletes artifacts, which can't be restored. In a terminal, `gh` asks once
+ for all of them. `--yes` skips the prompt and is required when `gh` can't
+ prompt. Without `--yes`, a failed lookup stops the command before the
+ prompt, and nothing is deleted. With `--yes`, every number is attempted.
+ - `gh issue artifact download {<issue> [<artifact-number>...] | <artifact-url>...} [--dir <dir> | --output <path>] [--clobber | --skip-existing] [--version N]`
+ writes the given artifacts, or every artifact on the issue, to files in
+ `--dir` (default `.`). Each file is named `<number>-<name>`, with path
+ separators, whitespace, control characters, and characters Windows forbids
+ replaced by `-`. Markdown documents end in `.md`, HTML documents in
+ `.html` or `.htm`, and links are saved as `.url` Internet Shortcut files,
+ which `create` and `edit` read back as links. An existing file fails that
+ artifact unless `--clobber` overwrites it or `--skip-existing` skips it.
+ `--output <path>` and `--version N` (an earlier version from the edit
+ history) need exactly one artifact number, and `--output -` prints its
+ stored body (a link's URL) with nothing else. Without numbers, an issue
+ with no artifacts is an error.
+ - The issue argument is a number or an issue URL, and a URL selects its own
+ repository. An artifact URL,
+ `https://github.com/<owner>/<repo>/issues/<issue>/artifacts/<number>`, can
+ replace the issue and number, and selects its repository the same way.
+ Artifact URLs in one command must be on the same issue. Pull requests are
+ refused, and so is GitHub Enterprise Server.
+ - `--json`/`--jq`/`--template` are available on `list` and `view` only. The
+ `id` field is a global ID that no command accepts; commands take `number`,
+ or `url`, the artifact's page on GitHub.
+ - `create`, `edit`, and `download` (except with `--output -`) report each
+ artifact on its own line. When piped, every line goes to stdout with five
+ tab-separated columns, empty ones included: status (`created`, `updated`,
+ `restored`, `written`, `skipped`, or `failed`), artifact number, name (the
+ file path for `download`), source (the file, URL, or `-` given to
+ `create`, or the `--body-file` value given to `edit`), and the reason for a
+ skip, a failure, or `--attach` files that didn't upload. In a terminal,
+ failures go to stderr. A failed artifact doesn't stop the others, and the
+ command exits 1 after any failure or failed upload. After `create` exits 1,
+ retry only the `failed` lines, since the others were created.
+ - `delete` reports on stderr. In a terminal, each deleted or failed artifact
+ gets a line. When piped, a deleted artifact prints nothing, and each
+ failure prints `<number>: <reason>`. Any failure exits 1.
+ - `create` and `edit` read and check every file before any request, so a
+ missing, empty, or binary file fails the whole command, and nothing is
+ saved. Standard input is read only through `--body-file -`, and `create`
+ refuses a `-` argument.
+ - `view`, `edit`, and `download` refuse an artifact whose type this version
+ of `gh` doesn't know, with a message to upgrade `gh`. `list` and `delete`
+ work with any type.
## Reading files and directories (`gh repo read-file` / `read-dir`)
Preview commands, subject to change. They read a repo's contents over the API
without cloning, and honor `--repo OWNER/REPO` (`-R`) and `--ref <branch|tag|commit>`
(default branch when omitted).
- `gh repo read-file <path> [--ref <ref>] [--output <path> [--clobber]] [--allow-escape-sequences] [--json <fields>] [--jq <expr>]`
prints a file's contents. In non-TTY contexts the raw bytes go straight to
stdout (pipe-friendly); binary files are written as-is when piped but are
refused on a TTY. By default, a file containing terminal escape sequences is
refused; pass `--allow-escape-sequences` to read it anyway. `--output <path>` (`-o`) writes to
disk instead of stdout (a trailing slash writes under a directory using the
remote file name; `--clobber` allows overwrite); writing to disk always
includes the raw bytes regardless of escape sequences. `--output` and `--json` are
mutually exclusive. `--json` fields include `name`, `path`, `gitSHA`, `size`,
`type`, `encoding`, and `content` (base64 encoded).
- `gh repo read-dir [<path>] [--ref <ref>] [--json <fields>] [--jq <expr>]`
lists a directory; with no path it lists the repo root. Non-TTY output is tab
separated as type, name, octal mode, and byte size. `--json` fields include
`name`, `path`, `type`, `gitType`, `mode`, `modeOctal`, `gitSHA`, `size`, and
`submodule`. A path pointing at a file errors and points you at `read-file`
(and vice versa).
## Fall back to `gh api` for anything `--json` doesn't expose
Sometimes useful data isn't on the typed commands. Examples:
- Review-thread comments on a PR: `gh api repos/{owner}/{repo}/pulls/{n}/comments`
(the `--comments` flag on `gh pr view` shows issue-level comments only).
- Arbitrary GraphQL: `gh api graphql -f query='...' -F var=value`.
- REST shortcuts: `gh api repos/{owner}/{repo}/...` - note the
`{owner}/{repo}` placeholder is filled in for you when run from a repo
with detected remotes; pass them literally if you want determinism.
## Authentication
- `gh auth status` prints the active host(s), user, and which env var (if
any) is being honored.
- `gh auth status --json` is supported.
## Other notes
- `gh pr checkout <n>` switches branches. Use `gh pr diff <n>` or
`gh pr view <n>` if you only need to read.
- `gh pr checkout <n> --worktree <path>` checks the PR out into a git worktree
at `<path>` instead of switching the current branch.
- `gh issue develop <n> --checkout` creates a linked branch for the issue and
checks it out. Add `--worktree <path>` to check that branch out into a git
worktree at `<path>` instead of switching the current branch;
`--worktree` requires `--checkout`, cannot be blank, and cannot be combined
with `--list`.
- `NO_COLOR`, `CLICOLOR_FORCE`, and `GH_FORCE_TTY` are honored. Set
`GH_FORCE_TTY=1` if you want TTY-style output (colors, tables, the
pager, interactivity) inside an agent harness; leave it unset unless needed.