Skip to content
Français
CI/CD & Automatisation medium

GitHub CLI (gh): managing GitHub from the terminal

40 min de lecture

Read this page in French

You are in the middle of coding, and you want to open a PR, check the status of a workflow or read the comments of an issue. Traditionally, you have to leave your terminal, open the browser, navigate to the right place.

GitHub CLI (gh) lets you do all of that without leaving the terminal. Creating PRs, triggering workflows, handling issues: everything is reachable from the command line.

What you will learn

  • Install and authenticate GitHub CLI on your machine
  • Handle Pull Requests: create, list, review, merge
  • Drive GitHub Actions: trigger, follow and debug the runs
  • Query the GitHub API with gh api and jq
  • Automate tasks inside your GitHub Actions workflows

What is GitHub CLI?

GitHub CLI is the official GitHub tool to interact with the platform from the terminal. It does not replace Git (which handles the code), but complements your workflow with the features specific to GitHub:

  • Pull requests
  • Issues
  • GitHub Actions (workflows, runs)
  • Releases
  • Gists
  • Repositories (creation, fork, clone)
  • Codespaces
  • And plenty more

Why use GitHub CLI?

The gain is not only comfort: every action becomes a reproducible command, so it can be scripted and replayed identically inside a GitHub Actions workflow. The table below puts the browser journey next to the equivalent gh command for the four most frequent operations.

TaskWithout ghWith gh
Open a PRBrowser, New PR, fill the formgh pr create
See the CI statusActions tab, click the rungh run list
Merge a PRBrowser, Merge buttongh pr merge
Trigger a workflowActions, Run workflow, clickgh workflow run

The key benefits:

  • Speed: no context switch
  • Scriptable: automate it with shell scripts
  • Consistency: the same workflow across all your projects
  • CI integration: usable inside GitHub Actions workflows

Installation

gh is a single binary, with no dependency to install alongside it, distributed by the package managers of every system. Pick the tab matching your machine: the command installs the binary, its manual page and its shell completion.

With Homebrew:

Fenêtre de terminal
brew install gh

Checking the installation:

Fenêtre de terminal
gh --version

The command must print the installed version. The official repository provides the latest stable version, for instance:

gh version 2.96.0 (2026-07-02)
https://github.com/cli/cli/releases/tag/v2.96.0

The distribution repositories (Debian, Ubuntu) often offer an older version: to get the latest commands, prefer the GitHub repository above or Homebrew.

Authentication

Before using gh, you have to authenticate against GitHub: every command goes through the API, which refuses anonymous calls beyond a few requests per hour. Authentication also defines the scopes of the token, so what gh is allowed to do: a token without the workflow scope will read the runs but will not be able to trigger any.

Fenêtre de terminal
gh auth login

An interactive wizard guides you:

  1. Where do you want to authenticate?

    • GitHub.com (the default option)
    • GitHub Enterprise Server
  2. Which protocol do you prefer for Git?

    • HTTPS (recommended)
    • SSH
  3. How do you want to authenticate?

    • Through the browser (the simplest)
    • With a personal access token

If you pick "browser", gh opens a page where you grant the access.

Checking the authentication:

Fenêtre de terminal
gh auth status

Example output:

github.com
Logged in to github.com account your-username
Git operations configured with https protocol
Token: gho_************************************
Token scopes: gist, read:org, repo, workflow

Authentication in scripts

For scripts or CI, use a token: the browser flow assumes an interactive session, which is impossible on a server. gh automatically reads the GH_TOKEN variable and uses it without writing anything to disk, which suits a disposable container.

Fenêtre de terminal
# Through an environment variable
export GH_TOKEN=ghp_xxxxxxxxxxxx
gh api user
# Or by passing the token
gh auth login --with-token < token.txt

Inside GitHub Actions, the GITHUB_TOKEN is available automatically:

- name: List the PRs
run: gh pr list
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Handling Pull Requests

The gh pr family covers the complete lifecycle of a Pull Request: creation, consultation, review, local checkout and merge. All those commands infer the repository and the current branch from the Git directory you are in, which avoids repeating owner/repo on every call.

Creating a PR

gh pr create pushes the current branch if needed, then opens the Pull Request against the default branch of the repository. Without --title or --body the command goes interactive; --draft creates a draft, which does not summon the reviewers until it is marked ready.

Fenêtre de terminal
# Interactive: gh asks questions
gh pr create
# In one line
gh pr create --title "Add feature X" --body "Description of the PR"
# With labels and reviewers
gh pr create --title "Fix bug" --label "bug" --reviewer "alice,bob"
# A draft PR
gh pr create --draft --title "WIP: New feature"

Listing the PRs

gh pr list shows the open PRs of the current repository by default, limited to the first thirty. The filters combine with each other, and --search accepts the same search syntax as the web interface: a filter already proven in the browser replays as it is on the command line.

Fenêtre de terminal
# Open PRs
gh pr list
# PRs with filters
gh pr list --state all # All of them (open, closed, merged)
gh pr list --author "@me" # My PRs
gh pr list --label "bug" # By label
gh pr list --search "is:open draft:false" # Advanced search

Viewing a PR

Without a number, gh pr view targets the PR attached to the current branch and prints its description, state and check results. The --json output feeds a script or jq, while --web switches to the browser for the commented diff.

Fenêtre de terminal
# Show the details
gh pr view 42
# In the browser
gh pr view 42 --web
# As JSON (for scripts)
gh pr view 42 --json title,state,reviews

Checking out a PR

Handy to test the code of a PR locally: gh pr checkout fetches the branch of the PR, including when it comes from a fork, and configures the tracking of the remote branch. You can then run the tests, add a commit and push it back to the original branch if the author allowed maintainers to edit their PR.

Fenêtre de terminal
gh pr checkout 42

That creates a local branch holding the code of the PR.

Merging a PR

gh pr merge asks GitHub to merge, and GitHub applies the branch protection rules: the command fails when a review or a required check is missing, it never forces its way through. --squash and --rebase override the mode configured on the repository for that one merge, and --delete-branch removes the remote branch along with its local copy.

Fenêtre de terminal
# Standard merge
gh pr merge 42
# Squash merge
gh pr merge 42 --squash
# Rebase merge
gh pr merge 42 --rebase
# Delete the branch after the merge
gh pr merge 42 --delete-branch

Reviewing a PR

gh pr review submits a formal review, not a plain comment: approval unlocks the merge on a repository requiring reviewers, and --request-changes blocks it until a new review lifts the request. GitHub refuses to let an author review their own PR: in that case, only gh pr comment works.

Fenêtre de terminal
# Approve
gh pr review 42 --approve
# Request changes
gh pr review 42 --request-changes --body "X should be fixed"
# Comment without approving
gh pr review 42 --comment --body "Good approach!"

GitHub Actions with gh

gh is particularly useful to handle GitHub Actions workflows. Two families of commands share the work: gh workflow handles the definitions (list, enable, trigger) and gh run handles the runs (follow, read the logs, rerun, cancel). Together, they replace the Actions tab for all the everyday diagnosis.

Listing the workflows

gh workflow list enumerates the files of .github/workflows/ recognised by GitHub, with their state (active or disabled) and their numeric identifier. That identifier is the one to prefer in a script: it survives a rename of the file, unlike the name of the workflow.

Fenêtre de terminal
# See the workflows of the repository
gh workflow list
# Example output:
# NAME STATE ID
# CI active 12345678
# Deploy active 12345679
# Security Scan active 12345680

Triggering a workflow

gh workflow run only works on a workflow declaring the workflow_dispatch trigger: without it, GitHub rejects the call. The parameters declared in inputs: are passed with -f key=value, and --ref picks the branch or the tag the definition of the workflow will be read from.

Fenêtre de terminal
# Trigger a manual workflow (workflow_dispatch)
gh workflow run ci.yml
# With inputs
gh workflow run deploy.yml -f environment=staging -f version=1.2.3
# On a specific branch
gh workflow run ci.yml --ref feature-branch

Seeing the runs

gh run list mixes every branch and every workflow by default, from the most recent run to the oldest: on an active repository, the --workflow and --status filters are indispensable to find anything. This is where you get the identifier expected by gh run view, rerun and cancel.

Fenêtre de terminal
# Latest runs
gh run list
# Runs of one specific workflow
gh run list --workflow ci.yml
# Runs in flight
gh run list --status in_progress
# Failed runs
gh run list --status failure

The details of a run

The --log option dumps the complete logs into the terminal, where grep handles them far faster than scrolling through the web interface, and --job narrows the output down to a single job. gh run watch follows a run in flight and returns when it ends, with a non-zero exit code on failure.

Fenêtre de terminal
# See one specific run
gh run view 12345678
# See the logs
gh run view 12345678 --log
# Logs of one specific job
gh run view 12345678 --log --job 98765432
# Follow it live
gh run watch 12345678

Rerunning a run

gh run rerun restarts the run on the same commit, which avoids the empty courtesy commit. --failed only replays the failed jobs and their dependants, a considerable gain on a wide matrix, and --debug enables the detailed logging of the runner.

Fenêtre de terminal
# Complete rerun
gh run rerun 12345678
# Rerun only the failed jobs
gh run rerun 12345678 --failed
# Rerun with debug enabled
gh run rerun 12345678 --debug

Cancelling a run

Cancellation is not instant: GitHub interrupts the jobs cleanly, and the steps marked if: always() still run. The run ends with the cancelled status, distinct from failure in the filters and the badges.

Fenêtre de terminal
gh run cancel 12345678

Downloading the artifacts

gh run download extracts every artifact of the run into a folder named after it, -n fetching only one. Once the retention period configured on the repository has passed, the artifacts are destroyed and the command fails.

Fenêtre de terminal
# List the artifacts of a run
gh run view 12345678 --json artifacts
# Download every artifact
gh run download 12345678
# Download one specific artifact
gh run download 12345678 -n build-output

Handling issues

The gh issue family follows the mechanics of gh pr: repository inferred from the current directory, creation, filtering and updates. Its main benefit is to open a ticket at the exact moment you notice the problem, without interrupting what you were doing in the terminal.

Creating an issue

With no argument, gh issue create opens your editor for the body of the ticket, then offers the labels and assignees defined on the repository. The @me value designates the authenticated account, which avoids hard-coding an identifier into a script.

Fenêtre de terminal
# Interactive
gh issue create
# In one line
gh issue create --title "Bug: crash on login" --body "Description..."
# With labels and an assignee
gh issue create --title "Feature request" --label "enhancement" --assignee "@me"

Listing and filtering

Repeating --label applies a logical AND: the example below only returns the tickets carrying both labels at once. For the criteria the options do not cover, dates or free text, --search accepts the complete GitHub search syntax.

Fenêtre de terminal
# Open issues
gh issue list
# My issues
gh issue list --assignee "@me"
# By label
gh issue list --label "bug" --label "priority:high"
# Search
gh issue list --search "is:open label:bug created:>2024-01-01"

Managing an issue

All those commands take the number of the ticket as an argument. gh issue edit is the one that serves in automation: it adds or removes labels and assignees without touching the title or the body, so without overwriting someone else's work.

Fenêtre de terminal
# View
gh issue view 123
# Comment
gh issue comment 123 --body "I am taking this bug!"
# Close
gh issue close 123
# Reopen
gh issue reopen 123
# Edit
gh issue edit 123 --add-label "in-progress" --add-assignee "alice"

Handling repositories

The gh repo family treats the repository as a GitHub object: creation, fork, cloning and metadata lookup. It relies on git for the local operations, but on top of that handles what git ignores entirely, such as the visibility of the repository or the link between a fork and its origin.

Creating a repository

With --source=., gh repo create attaches the existing local Git repository to the remote one it has just created, configuring the origin remote; --push sends the current branch straight away. In non-interactive mode, the visibility (--public or --private) is mandatory.

Fenêtre de terminal
# Interactive
gh repo create
# A new public repository
gh repo create my-project --public
# A new private repository with a description
gh repo create my-project --private --description "My great project"
# Create from the current directory
gh repo create --source=. --public --push

Cloning

gh repo clone accepts the short owner/repo notation and applies the protocol chosen during gh auth login. On a repository you have forked, it also adds the upstream remote towards the original, the step everyone forgets when cloning with git and without which the fork never resyncs.

Fenêtre de terminal
# Standard clone
gh repo clone owner/repo
# Clone into a specific folder
gh repo clone owner/repo ./my-folder

Forking

Run from an existing clone, the command offers to repoint the origin remote towards your fork and to keep the original repository as upstream. Run outside a repository, it simply creates the copy on your account.

Fenêtre de terminal
# Fork into your account
gh repo fork owner/repo
# Fork and clone
gh repo fork owner/repo --clone

Viewing a repository

gh repo view renders the README straight into the terminal, along with the description and the topics of the repository: enough to size up an unknown project, a Marketplace action for instance, without opening a tab.

Fenêtre de terminal
# Information about the current repository
gh repo view
# Information about another repository
gh repo view owner/repo
# In the browser
gh repo view --web

The GitHub API with gh

gh can call any endpoint of the GitHub API: gh api reuses the token already configured, sets the authentication and API version headers, then returns the raw JSON. It is the escape hatch when a feature has no dedicated command, or when you need a field gh pr view does not print. In exchange, you lose the formatting and the default values: the repository has to be named explicitly in the path.

Simple requests

The path passed to gh api is relative to https://api.github.com, no need to repeat the domain, and the default method is GET. The answer is the complete representation of the resource, often several dozen fields, hence the filtering shown just after.

Fenêtre de terminal
# Your profile
gh api user
# One specific repository
gh api repos/owner/repo
# The PRs of a repository
gh api repos/owner/repo/pulls

With jq to filter

The --jq option applies a jq filter inside gh: the jq binary does not have to be installed on the machine, which matters on a minimal runner. The output becomes line-by-line text, directly consumable by a shell loop, instead of the complete JSON.

Fenêtre de terminal
# The names of the branches
gh api repos/owner/repo/branches --jq '.[].name'
# The title and state of the PRs
gh api repos/owner/repo/pulls --jq '.[] | {title, state}'

POST requests

As soon as a -f field is present, gh api automatically switches to POST: no need to specify the method, -X only serves for PATCH or DELETE. Every -f sends a string; for a number or a boolean, use -F, otherwise the API rejects the request.

Fenêtre de terminal
# Create a comment on an issue
gh api repos/owner/repo/issues/123/comments \
-f body="A comment through the API"
# Create a label
gh api repos/owner/repo/labels \
-f name="priority:critical" \
-f color="FF0000"

Pagination

The GitHub API answers in pages of 30 items by default, 100 at most: without --paginate, a script counting the issues of a large repository silently misses most of them. The option chains the calls and concatenates the pages, at the cost of as many requests counted against your hourly quota.

Fenêtre de terminal
# Every page of results
gh api repos/owner/repo/issues --paginate --jq '.[].title'

Using it inside GitHub Actions

gh is pre-installed on GitHub runners. Use it for automated tasks. It does not authenticate itself for all that: every step calling it must receive GH_TOKEN in its env: block, and the job must ask for the permission matching the action (pull-requests: write to comment, contents: write to publish a release). That combination replaces most third-party actions that only make one API call.

Commenting on a PR automatically

This workflow posts a welcome message when a PR is opened. Two details make it safe: the job asks for the only permission gh pr comment needs, pull-requests: write, and the PR number travels through an env: block instead of being interpolated into run:, which rules out any template injection.

name: Comment on PR
on:
pull_request:
types: [opened]
# No rights by default: the job asks for the minimum
permissions: {}
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- name: Add welcome comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh pr comment "$PR_NUMBER" \
--body "Thanks for this PR! The team will review it within 48h."

Creating a release automatically

Triggered by pushing a v* tag, this workflow publishes the matching release. The --generate-notes option asks GitHub to compose the release notes from the PRs merged since the previous tag: no more changelog to maintain by hand. The contents: write permission is mandatory here, since a release modifies the repository.

name: Release
on:
push:
tags: ['v*']
# No rights by default: the job asks for the minimum
permissions: {}
jobs:
release:
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$TAG" \
--title "Release $TAG" \
--generate-notes

Triggering a deployment workflow

This step chains two workflows: the one that builds triggers the one that deploys, passing it the environment and the SHA to publish. The GITHUB_TOKEN is enough, because workflow_dispatch is one of the rare triggers insensitive to the anti-recursion protection of GitHub, which otherwise prevents a workflow from triggering another. The called workflow must declare the same inputs: as the ones passed with -f.

- name: Trigger deploy workflow
run: |
gh workflow run deploy.yml \
-f environment=production \
-f version=${{ github.sha }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Waiting for a run to finish

This step blocks until the CI is done: the nested command fetches the identifier of the most recent run of the targeted workflow, then gh run watch follows it to its conclusion. The exit code is non-zero when the run fails, which makes the calling step fail without writing a single waiting loop.

- name: Wait for checks to pass
run: |
gh run watch $(gh run list --workflow ci.yml --json databaseId --jq '.[0].databaseId')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

gh extensions

gh supports extensions to widen its capabilities: they are GitHub repositories whose name starts with gh-, which the CLI installs and exposes as extra subcommands. They run with your token and your rights: treat them like a Marketplace action, and look at who maintains them before installing.

Listing the available extensions

That command opens a text-mode interface and therefore requires an interactive terminal: in a script or in CI, go straight to gh extension install with the name of the repository.

Fenêtre de terminal
gh extension browse

Installing an extension

The installation is done by repository, in the owner/name form, and the subcommand becomes available immediately. Extensions do not update themselves: gh extension upgrade --all takes care of that.

Fenêtre de terminal
# The copilot extension
gh extension install github/gh-copilot
# The dashboard extension
gh extension install dlvhdr/gh-dash

These four extensions cover the recurring needs: the daily PR review, command line assistance, cleaning up merged branches and generating release notes. None of them is maintained by GitHub, apart from gh-copilot: the caution described above applies.

ExtensionDescription
gh-dashAn interactive dashboard for PRs and issues
gh-copilotAn AI assistant in the terminal
gh-poiCleans up the merged branches
gh-changelogGenerates changelogs

Configuration

gh reads its preferences from a single YAML file and offers gh config set to modify it without editing it by hand. Two settings really change everyday comfort: the editor opened to write the bodies of PRs and issues, and the aliases, which shorten the commands you type ten times a day.

The configuration file

The configuration is stored in ~/.config/gh/config.yml: that file can be versioned and copied as it is from one machine to another. It holds no secret, the authentication token living separately in hosts.yml or in the system keychain.

git_protocol: https
editor: vim
prompt: enabled
pager: less
aliases:
co: pr checkout
prl: pr list
prv: pr view

Custom aliases

Create shortcuts for the frequent commands: gh alias set writes the alias into the configuration file, so it applies to all your repositories. The arguments you pass to the alias are appended at the end of the expanded command, which lets you write gh co 42. gh alias list prints the aliases already defined.

Fenêtre de terminal
# Create an alias
gh alias set prl 'pr list'
gh alias set co 'pr checkout'
# An alias with arguments
gh alias set prw 'pr view --web'
# Use the alias
gh prl
gh co 42

Changing the default editor

With no setting, gh follows the EDITOR environment variable. The --wait option is mandatory with a graphical editor such as VS Code: without it, the command returns immediately and gh gets an empty message body.

Fenêtre de terminal
gh config set editor "code --wait"

Troubleshooting

Almost every gh failure traces back to authentication: a missing token, an expired one, or one whose scopes do not cover the requested action. The error message of the API often stays generic, it does not name the missing scope. So start systematically with gh auth status before looking elsewhere.

"permission denied" or "resource not accessible"

Symptom: gh cannot perform an action.

Fixes:

Fenêtre de terminal
# Check the scopes of the token
gh auth status
# Re-authenticate with more permissions
gh auth refresh -s workflow,repo,admin:org

"not logged in"

Symptom: gh asks for authentication.

Fix:

Fenêtre de terminal
gh auth login

Proxy problems

Behind a corporate proxy, gh follows the HTTPS_PROXY and NO_PROXY variables like most network tools. If the proxy inspects the encrypted traffic, it presents its own certificate: add its certificate authority to the trust store of the system, otherwise every command fails on a TLS error rather than on an authentication one.

Fenêtre de terminal
# Configure the proxy
export HTTPS_PROXY=http://proxy.example.com:8080
gh auth login

Key points

Efficiency

Manage GitHub without leaving the terminal: PRs, issues, workflows.

Scriptable

Automate it with shell scripts or inside your CI workflows.

The complete API

Reach the whole GitHub API with gh api.

Extensible

Add extensions for the advanced features.

The key points:

  1. gh auth login to authenticate (once and for all)
  2. gh pr for everything about Pull Requests
  3. gh run and gh workflow for GitHub Actions
  4. gh api to reach any endpoint
  5. Use aliases for your frequent commands

FAQ

These questions come up systematically during the install and the first uses, in particular about the boundary between git and gh and about authentication away from the workstation.

Next steps

  • actionlint: the linter validating the syntax of a workflow before gh workflow run triggers it for nothing.

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn