GitHub CLI (gh) Tutorial 2026: Replace Your Browser Workflow from the Terminal
Every developer knows the pattern: you finish a commit, push your branch, then switch to the browser to open a pull request, copy a URL to share with a reviewer, click through a dozen pages to check CI status, and finally go back to merge. The GitHub CLI (gh) eliminates all of that switching. Every action you take on GitHub.com has a terminal equivalent — and in many cases the terminal version is faster, scriptable, and more powerful.
This tutorial covers the full gh workflow in 2026: installation, authentication, repository operations, the complete issue and pull request lifecycle, GitHub Actions control, release management, aliases, CI/CD integration, and advanced search and API access. By the end you will be able to run an entire feature-branch-to-merged-PR workflow without touching a browser.
gh vs git: Two Complementary Tools
Before diving in, it is important to understand what gh is and is not.
git manages your local repository: commits, branches, merges, the object store. It talks to remote servers only for push/pull/fetch operations and does not understand GitHub-specific concepts like pull requests, issues, or Actions.
gh manages GitHub resources via the GitHub API. It knows nothing about your commit graph but understands everything GitHub offers: issues, PRs, CI runs, releases, gists, and even raw REST and GraphQL calls. The two tools are complementary — you still use git commit, git push, and git rebase. You use gh for everything that would otherwise require a browser tab.
Installation
macOS (Homebrew)
brew install gh
Upgrade later with brew upgrade gh.
Ubuntu and Debian (official apt repository)
GitHub maintains a signed apt repository. Add it and install:
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
&& wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
Windows (winget)
winget install --id GitHub.cli
Verify the installation on any platform:
gh --version
# gh version 2.50.0 (2026-04-15)
Authentication
Interactive browser OAuth
Run gh auth login and follow the prompts. Choose GitHub.com (or your GitHub Enterprise hostname), select HTTPS or SSH for git operations, then authenticate via browser:
gh auth login
# ? What account do you want to log into? GitHub.com
# ? What is your preferred protocol for Git operations? HTTPS
# ? Authenticate Git with your GitHub credentials? Yes
# ? How would you like to authenticate GitHub CLI? Login with a web browser
# ! First copy your one-time code: ABCD-1234
# Press Enter to open github.com in your browser...
Token-based authentication (headless / CI)
Set the environment variable GITHUB_TOKEN or pass a token directly:
echo "ghp_YourPersonalAccessTokenHere" | gh auth login --with-token
Check authentication status
gh auth status
# github.com
# ✓ Logged in to github.com as yourname (keyring)
# ✓ Git operations for github.com configured to use https protocol.
# ✓ Token: gho_****
# ✓ Token scopes: gist, read:org, repo, workflow
Repository Operations
Clone a repository
gh repo clone owner/repo
# equivalent to git clone but also sets up gh context
Create a new repository
# Public repo, initialized with a README
gh repo create my-new-project --public --add-readme
# Private repo with description, clone it locally after creation
gh repo create my-private-project --private \
--description "Work in progress" --clone
Fork a repository
gh repo fork cli/cli
# Forks to your account and optionally clones locally
gh repo fork cli/cli --clone
View repository information
gh repo view
# Shows repo description, topics, stars, latest activity
gh repo view torvalds/linux
# View any public repo
List your repositories
gh repo list
gh repo list --limit 50 --source # only repos you own, not forks
gh repo list --fork # only forks
Issues: Full Lifecycle
List issues
gh issue list
gh issue list --state open
gh issue list --label bug --assignee @me
gh issue list --milestone "v2.0"
gh issue list --author octocat
View an issue
gh issue view 42
gh issue view 42 --web # open in browser
Create an issue
gh issue create \
--title "Login page throws 500 on empty password" \
--body "Steps to reproduce: ..." \
--label "bug,priority:high" \
--assignee yourname \
--milestone "v1.3"
Use --body-file body.md to read the body from a file — useful for templated issues.
Edit an issue
gh issue edit 42 --title "Updated title"
gh issue edit 42 --add-label "needs-review" --remove-label "triage"
gh issue edit 42 --assignee newteammember
Comment on an issue
gh issue comment 42 --body "Confirmed on production. Assigning to @backend-team."
Close and reopen issues
gh issue close 42
gh issue close 42 --comment "Fixed in PR #87."
gh issue reopen 42
Pull Requests: The Core Workflow
Pull requests are where gh pays off most. You can open, review, check, and merge PRs without leaving the terminal.
Create a pull request
gh pr create \
--title "feat: add OAuth2 login support" \
--body "Resolves #42. Adds Google and GitHub OAuth2 providers." \
--reviewer alice,bob \
--label "feature,ready-for-review" \
--assignee @me \
--draft
Remove --draft when the PR is ready. Use --fill to auto-populate title and body from your commit messages.
List pull requests
gh pr list
gh pr list --state open
gh pr list --author @me
gh pr list --reviewer @me # PRs waiting for your review
gh pr list --label "ready-for-review"
gh pr list --base main # PRs targeting main
gh pr list --search "is:pr is:open draft:false"
View a pull request
gh pr view 87
gh pr view 87 --web
Check out a PR locally — the killer feature
This is one of the most useful things gh does. Given any PR number — including PRs submitted from a fork — gh fetches the branch and checks it out automatically:
gh pr checkout 87
Without gh, checking out a PR from a fork requires manually adding the contributor's remote, fetching their branch, and checking it out. gh pr checkout does all of that in one command. You can immediately run the code, reproduce a bug, or test a fix.
To return to your previous branch:
git checkout -
Review a pull request
Approve, request changes, or leave a comment review:
gh pr review 87 --approve
gh pr review 87 --request-changes --body "Please add unit tests for the OAuth callback."
gh pr review 87 --comment --body "Left a few inline notes. LGTM overall."
Check CI status
gh pr checks 87
# Shows each check: name, status (pass/fail/pending), duration, and a link
Wait for all checks to pass before merging:
gh pr checks 87 --watch # streams updates until all checks finish
Merge a pull request
Three merge strategies are available:
gh pr merge 87 --squash --delete-branch
gh pr merge 87 --rebase --delete-branch
gh pr merge 87 --merge --delete-branch
--delete-branch removes the remote branch after the merge — good hygiene for feature branches.
You can also merge interactively by running gh pr merge with no flags and answering the prompts.
GitHub Actions: Control from the Terminal
List workflows
gh workflow list
# ID Name State
# 12345 CI active
# 12346 Release active
# 12347 Nightly build disabled
Trigger a workflow manually
gh workflow run ci.yml
gh workflow run release.yml --field version=2.1.0 --field environment=production
The --field flag maps to workflow_dispatch inputs defined in your YAML.
List recent runs
gh run list
gh run list --workflow ci.yml
gh run list --branch feature/oauth --limit 5
Watch a run in real time
gh run watch 9876543
# Streams live log output until the run completes
View logs for a completed run
gh run view 9876543
gh run view 9876543 --log # full logs
gh run view 9876543 --log-failed # only failing steps
Rerun failed jobs
gh run rerun 9876543 --failed # rerun only the failed jobs
gh run rerun 9876543 # rerun the entire workflow
Releases
Create a release
gh release create v2.1.0 \
--title "Version 2.1.0" \
--generate-notes \
dist/app-linux-amd64 dist/app-darwin-arm64 dist/app-windows-amd64.exe
--generate-notes automatically drafts release notes from merged PRs since the previous tag. Upload any number of asset files as positional arguments.
Create a pre-release or draft
gh release create v2.2.0-beta.1 --prerelease --title "Beta 1"
gh release create v2.2.0 --draft --title "Version 2.2.0 (draft)"
List releases
gh release list
Download release assets
gh release download v2.1.0
gh release download v2.1.0 --pattern "*.tar.gz"
Aliases: Build Your Own Shortcuts
gh alias set lets you create short commands for long or frequently used operations.
Create aliases
# List your open PRs
gh alias set prs 'pr list --author @me'
# Open a PR for review and assign yourself
gh alias set review 'pr review --approve'
# Check out a PR by number
gh alias set co 'pr checkout'
# List open bugs assigned to you
gh alias set bugs 'issue list --label bug --assignee @me'
# Close an issue with a standard message
gh alias set done 'issue close --comment "Done. Closing."'
Use shell aliases for multi-step commands
gh alias set open-pr --shell \
'gh pr create --fill --assignee @me && gh pr view --web'
List all aliases
gh alias list
Remove an alias
gh alias delete bugs
Using gh in Scripts and CI/CD
Authentication in CI
In GitHub Actions, the GITHUB_TOKEN secret is available automatically. Pass it to gh:
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
--generate-notes \
dist/*
For scripts outside GitHub Actions, export the token:
export GITHUB_TOKEN="ghp_YourTokenHere"
gh auth status # verify it works
Automated release script
This script tags, creates a release with generated notes, and uploads build artifacts — all without the browser:
#!/usr/bin/env bash
set -euo pipefail
VERSION="${1:?Usage: release.sh <version>}"
ARTIFACTS_DIR="${2:-dist}"
echo "==> Tagging $VERSION"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
echo "==> Creating GitHub release"
gh release create "$VERSION" \
--title "Release $VERSION" \
--generate-notes \
"$ARTIFACTS_DIR"/*
echo "==> Done: $(gh release view "$VERSION" --json url -q .url)"
Save as scripts/release.sh, make it executable (chmod +x), and call it from your CI pipeline or locally.
Comment on a PR from CI
Useful for posting test coverage reports or benchmark results directly on the PR:
gh pr comment "$PR_NUMBER" \
--body "Coverage report: $(cat coverage-summary.txt)"
Auto-close stale issues from a script
gh issue list --label stale --json number -q '.[].number' | \
xargs -I{} gh issue close {} --comment "Closing as stale. Reopen if still relevant."
Advanced Features
Search across GitHub
gh search repos "language:python stars:>1000 topic:machine-learning"
gh search issues "is:open label:bug repo:cli/cli"
gh search prs "is:open review-requested:@me"
Gist management
gh gist create script.sh --public --desc "Useful deploy script"
gh gist list
gh gist view <gist-id>
gh gist clone <gist-id>
gh gist edit <gist-id>
Raw API access with gh api
For anything not covered by a dedicated subcommand, use gh api to call the REST or GraphQL API directly:
# REST: get rate limit info
gh api /rate_limit
# REST: list org members
gh api /orgs/my-org/members --jq '.[].login'
# GraphQL: get PR review decisions
gh api graphql -f query='
{
repository(owner: "cli", name: "cli") {
pullRequest(number: 1234) {
title
reviewDecision
reviews(last: 5) {
nodes { author { login } state }
}
}
}
}'
The --jq flag applies a jq expression to the JSON response, making it easy to extract specific fields in scripts.
Configuration
# Set your preferred editor
gh config set editor nvim
# Use SSH instead of HTTPS for git operations
gh config set git_protocol ssh
# Set default browser
gh config set browser firefox
# View all config
gh config list
End-to-End Workflow: Feature Branch to Merged PR
Here is a complete example of developing a feature and merging it using only the terminal.
Step 1: Start from a fresh branch
git checkout main && git pull
git checkout -b feat/add-oauth-login
Step 2: Write code, commit, push
# ... edit files ...
git add src/auth/oauth.py tests/test_oauth.py
git commit -m "feat: add OAuth2 login with Google and GitHub providers"
git push -u origin feat/add-oauth-login
Step 3: Open a PR
gh pr create \
--title "feat: add OAuth2 login" \
--body "Resolves #42. Adds Google and GitHub OAuth2 providers via authlib." \
--reviewer alice,bob \
--label "feature"
Step 4: Check CI
gh pr checks
# Wait and watch:
gh pr checks --watch
Step 5: Address review feedback
A reviewer requests changes. Check out the PR (useful if you switched branches):
gh pr checkout 87 # or just stay on the branch
Make the requested changes, commit, and push:
git add src/auth/oauth.py
git commit -m "fix: handle token refresh edge case per review"
git push
Step 6: Approve (as the reviewer)
gh pr review 87 --approve --body "LGTM, great work."
Step 7: Merge
gh pr merge 87 --squash --delete-branch
Step 8: Confirm and clean up locally
git checkout main
git pull
git branch -d feat/add-oauth-login
The issue linked in the PR body (Resolves #42) is automatically closed by GitHub when the PR merges.
Quick Reference
| Task | Command |
|---|---|
| Authenticate | gh auth login |
| Clone a repo | gh repo clone owner/repo |
| Create a PR | gh pr create --fill |
| Check out any PR | gh pr checkout NUMBER |
| Watch CI checks | gh pr checks --watch |
| Approve a PR | gh pr review NUMBER --approve |
| Merge with squash | gh pr merge NUMBER --squash --delete-branch |
| Trigger a workflow | gh workflow run name.yml |
| Watch a run | gh run watch RUN_ID |
| Create a release | gh release create vX.Y.Z --generate-notes |
| Add an alias | gh alias set NAME 'command' |
| Call the API | gh api /endpoint --jq '.field' |
Summary
gh is not a replacement for git — it is a complement that covers everything git cannot: the GitHub layer of your workflow. With gh you can manage issues, open and review pull requests, trigger and observe CI runs, publish releases, and call the raw GitHub API, all from one tool already in your PATH.
The features worth internalizing first are gh pr checkout (instant PR checkout from any fork), gh pr checks --watch (real-time CI feedback), and aliases (turning multi-flag commands into two-letter shortcuts). Together they eliminate the majority of context switches to the browser that slow down a day of active development.
All commands shown here are documented at cli.github.com/manual and the official GitHub CLI docs.