32blogby Studio Mitsu

GitHub CLI (gh): Practical Patterns for PRs, Issues, and API

Master the GitHub CLI (gh) with practical recipes for PRs, issue triage, and gh api scripting with jq. Drive GitHub from your terminal.

by omitsu16 min read
On this page

gh is the official GitHub CLI: a single Go binary that lets you create pull requests, triage issues, watch CI runs, and call the GitHub REST and GraphQL APIs without ever opening a browser tab. The killer features are gh pr create (one command from branch to open PR), gh api --paginate --jq (full REST/GraphQL access wired into shell pipelines), and gh run watch (live CI status that exits non-zero when the workflow fails — perfect for scripts). Once you install it and run gh auth login once, every later GitHub interaction stays inside your terminal.

Plain git ends at git push. Everything that happens after — opening the PR, picking reviewers, reading review comments, watching the CI matrix turn green, merging, deleting the branch, posting the release — has historically meant alt-tabbing to a browser, clicking through three menus, and breaking your flow every single time. The cost is invisible but enormous: a 2026 SmartScope analysis puts the average developer at 20 minutes a day on PR-related browser work, and points out that most of it is mechanical clicking that a single gh command replaces.

GitHub CLI is GitHub's own answer to the problem. It is written in Go — the only non-Rust tool in this CLI series — and that's the right call: Go has a first-class HTTP client, cross-compiles to a single binary on every platform, and reuses GitHub's existing internal SDKs. The result is a tool that feels like it was designed to live next to git itself. This article walks through the daily-driver patterns: PR workflows, issue triage, gh api scripting, and CI watching, with the recipes you'll actually use.

Why gh CLI bridges your terminal and GitHub

git was finished in 2005 and it still does its job perfectly. But the platform layer on top — pull requests, code review, issue tracking, GitHub Actions — was bolted on by a company a decade later, and for years the only way to use it was the website. That's the gap gh fills.

The leverage gh gives you comes from removing five specific kinds of context switch:

  1. Opening a PR. Without gh, you push your branch, copy the URL the remote prints, paste it in a browser, click "compare & pull request," fill in the form, click submit. With gh pr create --fill, the title and body are pulled from your commits and the PR opens in roughly half a second.
  2. Reading review comments. The default GitHub UI buries inline comments under tabs and timelines. gh pr view 123 --comments dumps every comment, threaded, into your terminal where you can grep, scroll, and copy code.
  3. Checking CI status. gh pr checks shows the live state of every workflow attached to a PR, and gh run watch sits on a live workflow with a --exit-status flag that lets you chain it with && notify-send done.
  4. API scripting. Need to list every open issue across an organization, every release tag, every PR labeled bug? gh api --paginate --jq is curl + jq + an auth token + pagination in one command, with the auth handled by gh auth login once.
  5. Custom workflows. gh extension install lets you bolt on community extensions or write your own — gh dash for a TUI dashboard, gh poi for branch cleanup, gh-pr-await for PR change polling.

Every one of these would be a half-day shell script if you had to do it from scratch with curl and a hand-rolled OAuth flow. gh ships with all of that already wired up.

Install gh and authenticate

Installation is one package and one interactive login. The official installation page lists every platform; the short version is below.

powershell
# winget (built into Windows 11)
winget install GitHub.cli

# or Scoop
scoop install gh

# or Chocolatey
choco install gh

After installation, run gh --version and then authenticate once:

bash
gh auth login

This walks you through an interactive prompt: pick GitHub.com (or a GitHub Enterprise host), pick HTTPS or SSH for git operations, and choose Login with a web browser. gh prints an 8-character device code and opens the browser — you paste the code, approve the OAuth scopes, and the token is stored locally in the OS keychain (macOS Keychain, Windows Credential Manager, libsecret on Linux).

Once authenticated, two commands sanity-check the install:

bash
gh auth status
gh api user --jq '.login'

The first prints which host you're logged into and which scopes the token holds. The second hits the GitHub API as you and prints your username — if both work, every other command in this article will work.

PR workflows: from gh pr create to gh pr merge

This is the section that pays for the install. The full daily PR loop fits into about six commands.

Creating a PR

After git push -u origin my-branch, the basic create is one line:

bash
gh pr create --fill

--fill reads the most recent commit message and uses it as the title and body. If you want explicit control:

bash
gh pr create \
  --title "Fix delta hover-state regression" \
  --body "Closes #142. The hover handler was firing twice." \
  --base main \
  --reviewer omitsu-dev,@studio-mitsu/reviewers \
  --label bug,frontend \
  --assignee @me \
  --draft

The official examples cover the rarer flags (--project, --milestone, --template). One pattern worth remembering: --reviewer accepts both individuals and @org/team slugs in the same flag, so a single line can request review from a person and a whole team at once.

If your branch isn't pushed yet, gh pr create will offer to push it for you — answer yes and the branch lands on the remote and the PR opens in the same step.

Viewing and reading a PR

bash
gh pr view 142                  # Markdown summary in your terminal
gh pr view 142 --comments       # Plus every review comment, threaded
gh pr view 142 --web            # Open in browser if you need the rich UI
gh pr diff 142                  # Plain unified diff
gh pr diff 142 | delta          # Pipe to delta for syntax-highlighted side-by-side

That last line is the reason we covered delta in the previous article. gh pr diff | delta is the closest a terminal gets to the GitHub Files Changed tab — and it's faster.

Reviewing and approving

bash
gh pr checkout 142              # Locally check out the PR branch (handles forks)
gh pr review 142 --approve --body "LGTM, ship it"
gh pr review 142 --request-changes --body "See inline comments"
gh pr review 142 --comment --body "Some questions below"

gh pr checkout is the one to remember — it handles cross-repo forks (user:branch) and configures the upstream tracking branch correctly so subsequent git push works without surprises.

Merging and cleanup

bash
gh pr merge 142 --squash --delete-branch
gh pr merge 142 --merge --auto      # enable auto-merge: waits for required checks
gh pr merge 142 --rebase --delete-branch

The --auto flag is the one that changes how you work: enable it on a draft PR, mark the PR ready for review, and gh will merge it the moment all required checks pass. No more babysitting CI.

Issue workflows: search, triage, and comment

Issues get less love than PRs, but gh issue is where bulk maintenance gets fast. The issue search syntax is the same as the web UI, which means everything you already know transfers.

bash
# List open issues assigned to you
gh issue list --assignee @me --state open

# Search across labels and authors
gh issue list --search "is:open label:bug author:omitsu-dev"

# Create one quickly
gh issue create --title "Hover state breaks on mobile Safari" \
  --body "Reproduced on iOS 18.3. See screenshot." \
  --label bug,mobile

# Read it
gh issue view 142 --comments

# Comment, close, reopen
gh issue comment 142 --body "Fixed in #198."
gh issue close 142 --reason completed
gh issue reopen 142

The combination that earns its keep is bulk operations through pipes. Suppose you want to close every issue tagged wontfix that hasn't been touched in six months:

bash
gh issue list \
  --label wontfix \
  --search "updated:<2025-10-10" \
  --json number \
  --jq '.[].number' |
  xargs -I{} gh issue close {} --reason "not planned"

Two things to notice. First, --json returns structured JSON instead of the human table — that's the magic flag that turns gh from a UI into a scripting tool. Second, the result is piped through xargs exactly the way the xargs guide describes. This is the moment gh stops being a convenience and starts being a force multiplier.

gh api: scripting GitHub with jq and pagination

gh api is the part that turns gh from a productivity tool into the foundation of your automation. It is curl plus authentication plus pagination plus JSON filtering, all in one command, with your existing gh auth token.

The basic shape:

bash
gh api repos/cli/cli/releases/latest --jq '.tag_name'
# v2.89.0

That's the whole pattern. gh api PATH is a GET against https://api.github.com/PATH, with your token already attached. --jq runs the response through jq and prints the result. No headers, no pagination, no auth — gh handles all of it.

Filtering and templating

bash
# List the last 10 releases of a repo, tag + date
gh api repos/cli/cli/releases \
  --jq '.[0:10] | .[] | "\(.tag_name)\t\(.published_at)"'

# Same data via Go templates instead of jq
gh api repos/cli/cli/releases \
  -t '{{range .}}{{.tag_name}}{{"\t"}}{{.published_at}}{{"\n"}}{{end}}'

The -t flag is a sleeper feature: when you don't have jq installed (a stripped-down container, for instance), Go templates do most of what you need.

Pagination

GitHub paginates almost every list endpoint at 30 items by default. To get all of them:

bash
gh api --paginate repos/cli/cli/issues --jq '.[].number'

--paginate follows the Link: rel="next" header until the API runs out of pages. There is one current limitation worth knowing about: per issue #10459, --paginate --slurp --jq cannot be combined. Use either --paginate --jq (which runs jq once per page) or --paginate --slurp (which collects everything into one JSON array first, then you pipe through jq separately).

POST, PATCH, DELETE

bash
# Comment on an issue
gh api repos/omitsu-dev/32blog/issues/142/comments \
  -f body='Fixed in 0a1b2c3.'

# Add a label
gh api repos/omitsu-dev/32blog/issues/142/labels \
  -f labels[]=bug -f labels[]=frontend

# DELETE a release
gh api -X DELETE repos/omitsu-dev/32blog/releases/12345

-f sends a static string field, -F sends a typed field (numbers, booleans, file contents via @path). The @- syntax even reads stdin, which makes cat body.md | gh api ... -F body=@- trivial.

GraphQL

The same command speaks the GraphQL endpoint. The official GitHub blog walkthrough is worth bookmarking.

bash
gh api graphql -F owner=cli -F name=cli -f query='
  query($owner:String!, $name:String!) {
    repository(owner:$owner, name:$name) {
      stargazerCount
      defaultBranchRef { name }
    }
  }
' --jq '.data.repository'

GraphQL pagination needs a pageInfo { hasNextPage, endCursor } clause and the magic $endCursor: String variable — see the gh api manual for the full template. With those in place, --paginate --slurp walks every page automatically.

gh run watch, extensions, and aliases

The last layer is everything that turns gh into your personal terminal dashboard.

Watching CI

bash
gh run list                          # Recent workflow runs
gh run view 1234567890               # One run, with job/step breakdown
gh run view 1234567890 --log         # Stream all logs
gh run view --log-failed             # Just the failing steps
gh run watch                         # Live status of the latest in-progress run
gh run watch --exit-status           # Exit non-zero if it fails (CI-friendly)
gh run rerun 1234567890 --failed     # Rerun only the failed jobs
gh pr checks                         # Status of every check on the current PR

gh run watch --exit-status is the keystone for scripts. Per the gh run watch manual, it polls every 3 seconds (configurable with -i) and exits non-zero if the workflow fails — so you can chain it with anything:

bash
git push && gh run watch --exit-status && notify-send "Build green"

The notification fires when the build is actually green, not when you remembered to check the browser tab.

Extensions

bash
gh extension install dlvhdr/gh-dash       # TUI dashboard for PRs and issues
gh extension install yusukebe/gh-markdown-preview
gh extension list
gh extension upgrade --all
gh extension remove gh-dash

The extension manual covers writing your own — an extension is just an executable named gh-foo on your $PATH, so a one-line bash script counts.

Aliases

bash
gh alias set prs 'pr list --author @me'
gh alias set bugs 'issue list --label bug --state open'
gh alias set ship 'pr merge --auto --squash --delete-branch'
gh alias list

Aliases live in ~/.config/gh/config.yml and are simple string substitutions. For more flexible shorthand (multi-step workflows, conditional logic), pair this with the shell alias guide and write a wrapper function instead.

About the gh-copilot extension

If you're searching for gh copilot suggest in 2026, the answer has changed: the gh-copilot extension was retired on October 25, 2025. The replacement is the standalone GitHub Copilot CLI (binary name copilot, currently at v1.0.22 as of April 9, 2026), which is no longer a gh extension at all — it's a separate agentic assistant you install with brew install copilot-cli or npm install -g @github/copilot. The old ghcs/ghce aliases still work for users who installed the extension before retirement, but new installs should use the standalone tool.

FAQ

Q. Is gh the same as git? No. git manages local commits and branches; gh manages everything that lives on GitHub itself — PRs, issues, releases, workflow runs, and the API. They're complementary. gh doesn't replace any git command; it adds the GitHub-specific layer on top.

Q. Does gh work with GitHub Enterprise Server? Yes. Pass --hostname your-ghes.example.com to gh auth login and every subsequent command targets that host. You can be logged into multiple hosts at once and switch between them with gh auth switch.

Q. Can gh create draft PRs from a script? Yes — gh pr create --draft --fill is the one-liner. To promote a draft to ready: gh pr ready 142.

Q. How do I list every PR I've ever opened across all repos? gh search prs --author @me --state all --limit 1000. The gh search family hits GitHub's global search and is the right tool for cross-repo queries — much faster than looping through gh pr list per repository.

Q. How do I read the output of gh api in a shell variable? Use command substitution and --jq to pull out exactly the field you need: tag=$(gh api repos/cli/cli/releases/latest --jq .tag_name). Always quote the variable when you reuse it.

Q. What's the difference between gh run watch and gh pr checks? gh pr checks is a one-shot status report on the current PR's workflows. gh run watch is a live tail that blocks until a single workflow run finishes — and exits non-zero on failure, which makes it scriptable.

Q. Is gh written in Go because GitHub is a Go shop? Mostly. GitHub's internal services are a mix of Ruby and Go, but the CLI was specifically built in Go because Go produces a single static binary per platform with no runtime dependency, and its standard library has a first-class HTTP client. The same trade-offs are why most modern CLI tools pick either Rust or Go over Ruby/Python.

Q. Will gh copilot suggest still work? Only if you installed the gh-copilot extension before October 25, 2025 and never uninstalled it. New installs are blocked because the extension is retired. Use the standalone GitHub Copilot CLI instead — it's the supported path going forward.

Wrapping Up

gh is the missing half of git. The split is historical (Git predates GitHub by three years and predates pull requests by six), but the practical result is that for most developers in 2026, half of the daily GitHub workflow lives in a browser tab nobody asked for. gh collapses that browser tab back into the terminal where everything else already is.

The four commands that earn the install:

  1. gh pr create --fill — open a PR from your latest commit in one keystroke.
  2. gh pr diff | delta — read the diff like a code review tool, in your terminal.
  3. gh api --paginate --jq — script GitHub like it's a local database.
  4. gh run watch --exit-status — block on CI in scripts and shell loops.

Pair gh with delta for diffs, bat for viewing files inside PR checkouts, ripgrep for searching across cloned repos, and fzf for picking PR numbers interactively, and your daily GitHub work fits inside one terminal window. That's the goal of the modern CLI series — and gh is the piece that ties the whole platform layer to the rest of your workflow.