GitHub: top commands in gh, the official GitHub CLI

gh (pronounced… “guh”?) is GitHub’s official command line interface (CLI) tool. It lets you interact with GitHub directly from the command line, supporting most key features across more than 25 commands and over 200 subcommands. It’s also great for bridging the terminal-browser gap, allowing you to jump to pages relevant to your current context, such as the PR for the current branch.
Two weeks ago, I published my book, Boost Your GitHub DX, which covers many ways to improve your productivity when using GitHub. Several chapters in the book discuss installing and using gh, from basic installation to advanced API usage.
This post distills some of the gh commands covered in thn book into a “top picks” list of the eight commands that I use regularly to boost my productivity. Let’s go!
1. Open things in the browser with gh browse
gh browse opens various web interface pages for a repository. Like most gh commands, it defaults to using the repository that you’re currently in, but it can also target other repositories with the -R (--repo) option.
gh browse helps bridge the worlds of command line and web, and can help you grab links to share with your collaborators. I use it quite often, even for just opening the repository home page and navigating from there.
Repository pages
Without any arguments, gh browse opens the current repository’s home page in the browser:
$ gh browse
Opening https://github.com/django/django in your browser.
Add one of these options to open one of the repository’s other pages:
-p(--projects) for Projects.-r(--releases) for Releases.-s(--settings) for Settings.-w(--wiki) for the Wiki.
Issues and pull requests
Pass a number to open the given issue or pull request:
$ gh browse 17554
Opening https://github.com/django/django/issues/17554 in your browser.
Issues and pull requests share the same numbering system, which is why you can pass either without disambiguation.
Files and directories
Pass a path to open a specific file or directory:
$ gh browse django/db/models/base.py
Opening https://github.com/django/django/tree/main/django/db/models/base.py in your browser.
$ gh browse .
Opening https://github.com/django/django/tree/main in your browser.
Paths are resolved relative to the current working directory.
Add : and a line number onto a file name to open it at that line:
$ gh browse django/db/models/base.py:59
Opening https://github.com/django/django/blob/main/django/db/models/base.py in your browser.
View the file on a different branch with -b (--branch) and a branch name:
$ gh browse -b stable/5.2.x django/db/models/base.py
Opening https://github.com/django/django/tree/stable/5.2.x/django/db/models/base.py in your browser.
Confusingly, -b doesn’t just accept branches, but also tags and commit SHAs. So, to view a file on a specific commit:
$ gh browse -b 72c0359dda8 django/db/models/base.py
Opening https://github.com/django/django/tree/72c0359dda8/django/db/models/base.py in your browser.
Combine with git rev-parse @ to open the current file at the current commit:
$ gh browse -b $(git rev-parse @) django/db/models/base.py
Opening https://github.com/django/django/tree/240421c7c4c81fe5df26274b807266bd4ca73d7f/django/db/models/base.py in your browser.
This command requires that the current commit has been pushed to the remote repository.
(More on that Git command in this blog post.)
(There is also a -c option for selecting by commit SHA, but it’s redundant given -b actually accepts commit SHAs.)
2. Open some other things in the browser with -w (--web)
gh browse doesn’t cover every “open in the browser” use case. Instead, some subcommands have a -w (--web) option to open their respective objects’ pages in the browser. Keep an eye out for these options on your favourite commands. Here’s my top selection:
gh issue list -w
Opens the issue list for the current repository:
$ gh issue list -w
Opening https://github.com/django/django/issues in your browser.
gh pr view -w
Opens the pull request for the current branch:
$ gh pr view -w
Opening https://github.com/django/django/pull/17554 in your browser.
Fails if there are no open pull requests for the current branch.
gh release view -w
Opens the latest release for the current repository:
$ gh release view -w
Opening https://github.com/typeddjango/django-stubs/releases/tag/5.1.3 in your browser.
Pass a tag name to open a different one.
gh workflow view -w
Opens the run list for a GitHub Actions workflow. Without any arguments, it prompts you to select from the available workflows:
$ gh workflow view -w
? Select a workflow [Use arrows to move, type to filter]
> Accessibility (accessibility.yml)
Benchmark (benchmark.yml)
Docs (docs.yml)
...
Otherwise, you can pass a workflow filename (like .github/workflows/ci.yml), name (like CI), or ID (like 171290076). Filenames are the easiest because you can use tab completion to find them within .github/workflows/:
$ gh workflow view -w .github/workflows/docs.yml
Opening https://github.com/django/django/actions/workflows/docs.yml in your browser.
gh run view -w
Opens the summary for a GitHub Actions run.. Without any arguments, it lets you select from the latest runs:
$ gh run view -w
? Select a workflow run [Use arrows to move, type to filter]
> - Fixed missing comma in reST class directive in /docs/ref/checks.txt, Benchmark [ticket_typo] 14m56s ago
✓ Fixed missing comma in reST class directive in /docs/ref/checks.txt, Docs [ticket_typo] 14m56s ago
✓ Fixed missing comma in reST class directive in /docs/ref/checks.txt, New contributor message [ticket_typo] 14m57s ago
✓ Fixed missing comma in reST class directive in /docs/ref/checks.txt, Labels [ticket_typo] 14m57s ago
✓ Fixed #36202 -- Clarified that contains lookup on JSONField matches arrays by containment., Docs [ticket_36202] 56m30s ago
...
Otherwise, you can pass a run ID:
$ gh run view -w 13466220209
Opening https://github.com/django/django/actions/runs/13466220209 in your browser.
3. Clone a repository with gh repo clone
gh repo clone clones a repository locally, wrapping git clone with some GitHub-specific niceties. Pass the repository reference in the <owner>/<repo> format to clone that repository. For example, to clone Django:
$ gh repo clone django/django
Cloning into 'django'...
remote: Enumerating objects: 551945, done.
...
For your personal repositories, you can drop the <owner> part and provide only <repo>. For example, to clone a personal repository called “home”:
$ gh repo clone home
Cloning into 'home'...
remote: Enumerating objects: 201, done.
...
This command also accepts GitHub URLs, useful for copy-pasting from your browser’s address bar:
$ gh repo clone https://github.com/django/django
Like a vanilla git clone, you can provide a second argument to specify the directory name to clone into. For example, to clone the gh repository itself, cli/cli, into a directory called github-cli:
$ gh repo clone cli/cli github-cli
One neat feature is that if you clone a fork, the command will also add the original repository as a remote called upstream. For example, to clone my Django fork:
$ gh repo clone adamchainz/django
Cloning into 'django'...
remote: Enumerating objects: 488529, done.
...
! Repository django/django set as the default repository. To learn more about the default repository, run: gh repo set-default --help
Note the message about setting the default repository. This means that some gh commands in the repository will resolve against django/django, not adamchainz/django, for example, when opening a pull request. That’s typically the behaviour you want in a fork, where you aim to contribute back to the upstream repository.
Inside the repository, you can see remotes for both the fork (“origin”) and the original repository (“upstream”):
$ cd django
$ git remote -v
origin https://github.com/adamchainz/django.git (fetch)
origin https://github.com/adamchainz/django.git (push)
upstream https://github.com/django/django.git (fetch)
upstream https://github.com/django/django.git (push)
You can also pass options through to the underlying git clone command by providing them after a -- separator. For example, to make a shallow clone with --depth 1:
$ gh repo clone django/django -- --depth 1
Thanks to its easier UI and extra configuration for forks, gh repo clone is my favoured way of cloning repositories from GitHub.
4. Start working on an issue with gh issue develop -c
Use gh issue develop with its -c (--checkout) option and an issue number to start working on that issue in a new branch:
$ gh issue develop -c 708
github.com/adamchainz/home/tree/708-sharpen-cheese-knife
From https://github.com/adamchainz/home
* [new branch] 708-sharpen-cheese-knife -> origin/708-sharpen-cheese-knife
The command creates a new linked branch based on the issue number you provide, with a name based on the issue number and title. It makes the branch remotely and locally, so you’re set to git push. Unfortunately, it only works within a repository you have write access to, so you cannot use it when working on a fork of an upstream repository.
If you later run gh pr create on such a linked branch, the pull request it creates will be automatically linked to the issue. This can lead to quite a seamless workflow where gh handles Git branches for you.
5. Create a pull request with gh pr create
Run gh pr create to create a pull request from the command line.
This command has several different modes, which we’ll now look at, after one universal feature.
Push prompt
Whichever mode you use, if you run gh pr create on a branch that has not been pushed to GitHub, it will prompt you to do so first:
$ gh pr create
? Where should we push the 'plant-daffodils' branch? [Use arrows to move, type to filter]
> adamchainz/home
Skip pushing the branch
Cancel
This can be quite convenient, as it allows you to push and create the pull request in one go.
Interactive mode
By default, gh pr create uses a series of interactive prompts to create a pull request. After the optional push prompt, it asks you for a title:
$ gh pr create
? Where should we push the 'plant-daffodils' branch? adamchainz/home
Creating pull request for plant-daffodils into main in adamchainz/home
? Title (required) (Plant a lovely bunch of Daffodils)
The default value in parentheses is pulled from the first line of your most recent commit message. Press Enter to accept it or type a different title first.
The next prompt asks for the body (description), which is optional:
? Title (required) Plant a lovely bunch of Daffodils
? Body [(e) to launch zed, enter to skip]
Press either e to open your text editor (zed in my case) or Enter to skip adding a body. The next prompt presents four ways forward, plus a cancel option:
? Body <Received>
? What's next? [Use arrows to move, type to filter]
> Submit
Submit as draft
Continue in browser
Add metadata
Cancel
Asides from Cancel, the options are:
Submit
This option creates the pull request and displays its URL:
? What's next? Submit remote: remote: To github.com:adamchainz/home.git * [new branch] HEAD -> plant-daffs branch 'plant-daffs' set up to track 'origin/plant-daffs'. https://github.com/adamchainz/home/pull/721
Submit as draft
This option works similarly to Submit, but creates a draft pull request.
Continue in browser
This option opens the pull request in your web browser, where you can further edit it.
? What's next? Continue in browser Opening https://github.com/adamchainz/home/compare/main...plant-daffs in your browser.
The form is pre-filled with the title and body you provided, passed through URL query parameters. From there, you can fill in other fields and then finally create the pull request.
Add metadata
This option leads you to another prompt to select metadata categories to add:
? What's next? Add metadata ? What would you like to add? [Use arrows to move, space to select, <right> to all, <left> to none, type to filter] > [ ] Reviewers [ ] Assignees [ ] Labels [ ] Projects [ ] Milestone
Select some, then you’ll be led through their individual prompts, like:
? What would you like to add? Labels ? Labels [Use arrows to move, space to select, <right> to all, <left> to none, type to filter] > [ ] kitchen [ ] garden [ ] expensive
After you’ve filled in the metadata, you can submit, submit as draft, or cancel:
? Labels bug ? What's next? [Use arrows to move, type to filter] > Submit Submit as draft Cancel
Direct mode
Use these options to set the title and body for the pull request, skipping the interactive prompts:
| Option | Field | Notes |
|---|---|---|
-t (--title) | Title | |
-b (--body) | Body (description) | |
-F (--body-file) | Body from a file | Provide a file path. |
If you don’t fill in both the title and the body, the command continues to interactive mode, prompting for whichever field is missing:
$ gh pr create -t 'Install cheese fridge'
Creating pull request for desk into main in adamchainz/home
? Body [(e) to launch zed, enter to skip]
But if you do fill in both fields, the command skips all prompts and creates the issue directly:
$ gh pr create -t 'Install cheese fridge' -b 'Spending cheddar to save cheddar. Fixes #709.'
Creating pull request for desk into main in adamchainz/home
remote:
remote:
To github.com:adamchainz/home.git
* [new branch] HEAD -> desk
branch 'desk' set up to track 'origin/desk'.
https://github.com/adamchainz/home/pull/722
This skip trick works even if you pass an empty body, which may be useful for rapid work in less well-crafted repositories:
$ gh pr create -t 'Install cheese fridge' -b ''
Fill mode
Use these options to fill the title and body from the commit messages on the current branch:
| Option | Title | Body |
|---|---|---|
-f (--fill) | Branch name | List of the first lines of all commit messages |
--fill-first | First line of the first commit message | Rest of the first commit message |
--fill-verbose | Branch name | List of all commit messages |
Let’s demonstrate each option with an example. Say we are on a branch called cheeseboard that has two commits:
Add new cheese board It’s giant! Fixes #710.
Wash it before use Oops, missed that.
Using -f (--fill) would create a pull request with:
Title:
cheeseboard
(The branch name.)
Body:
* Add new cheese board * Wash it before use
Using --fill-first would create a pull request with:
Title:
Add new cheese board
Body:
It’s giant! Fixes #710.
Using --fill-verbose would create a pull request with:
Title:
cheeseboard
(The branch name.)
Body:
* Add new cheese board It’s giant! Fixes #710. * Wash it before use Oops, missed that.
Pick a fill mode that works for you, depending on how you write your commit messages.
I find --fill-first works best for my workflows, as I typically write a detailed first commit message that I can copy into the pull request. When I iterate, I’ll amend that commit with git commit --amend, or pile on smaller iterative commits with less-useful messages.
Use a fill option alone to immediately create the pull request:
$ gh pr create --fill-first
? Where should we push the 'cheeseboard' branch? adamchainz/home
Creating pull request for cheeseboard into main in adamchainz/home
remote:
remote:
To https://github.com/adamchainz/home.git
* [new branch] HEAD -> cheeseboard
branch 'cheeseboard' set up to track 'origin/cheeseboard'.
https://github.com/adamchainz/home/pull/723
Alternatively, add -e for editor mode (covered next), allowing you to edit the generated title and body first:
$ gh pr create --fill-first -e # Opens editor
Editor mode
Add -e (--editor) to use editor mode, which has you write the title and body in your text editor. gh will open a temporary Markdown file for you to edit, using a template like:
|cheeseboard
* Add new cheese board
* Wash it before use
------------------------ >8 ------------------------
Please Enter the title on the first line and the body on subsequent lines.
Lines below dotted lines will be ignored, and an empty title aborts the creation process.
The “|” above indicates that your cursor will be positioned on the first line.
The file works similarly to a Git commit message file, where gh ignores the “scissors line” and everything after it (>8 is a pair of ASCII art scissors).
Save the file and close it to continue, after which the command creates the issue and displays its URL:
$ gh pr create -e
? Where should we push the 'cheeseboard' branch? adamchainz/home
Creating pull request for cheeseboard into main in adamchainz/home
remote:
remote:
To github.com:adamchainz/home.git
* [new branch] HEAD -> cheeseboard
branch 'cheeseboard' set up to track 'origin/cheeseboard'.
https://github.com/adamchainz/home/pull/723
The default message is generated with the same logic as -f (--fill), taking the branch name as the title and the commit messages as the body. Pass --fill-first or --fill-verbose to use their logic instead. --fill-verbose may be the best in this case, since it contains the most details to pick from.
The -t (--title) and -b (--body) options work with editor mode. Any title or body from the options will be added to the file, ready to edit.
This mode is my preferred one, since I like using the familiar environment of my editor.
You can make editor mode the default by enabling the prefer_editor_prompt configuration option:
$ gh config set prefer_editor_prompt enabled
Then, running gh pr create (without -e) will enter editor mode by default. (This configuration option also affects gh issue create.)
Web mode
Add -w (--web) to open the repository’s Open a pull request page in your web browser:
$ gh pr create -w
Opening https://github.com/adamchainz/home/compare/main...toy_boxes in your browser.
There, you can fill in the form and submit to create the pull request.
The super cool feature is that command line options like -t (--title) and -b (--body) prefill the form fields in the web interface. For example, passing -t:
$ gh pr create -w -t "Reorganize toy boxes"
Opening https://github.com/adamchainz/home/compare/main...toy_boxes in your browser.
…fills the title field:

This mode is great for retaining the flexibility of the web form, while keeping in flow from the command line.
Metadata options
Use these options to fill in metadata fields:
| Option | Field | Notes |
|---|---|---|
-d (--draft) | Draft | Mark pull request as a draft. |
-a (--assignee) | Assignee | Takes a username. Use @me for yourself. Repeat for multiple assignees. |
-B (--base) | Base branch | The branch your pull request will be merged into. |
-l (--label) | Label | Repeat for multiple labels. |
-p (--project) | Project | |
-m (--milestone) | Milestone | |
-r (--reviewer) | Reviewer | Takes a username. Repeat for multiple reviewers. |
These options work whichever mode you use: interactive, direct, fill, or editor.
6. Check out an existing pull request with gh pr checkout
Run gh pr checkout to check out an existing pull request in a local branch, given its number:
$ gh pr checkout 719
remote: Enumerating objects: 402208, done.
...
From https://github.com/adamchainz/home
Switched to branch 'washing_machine'
…or URL, for easy copy-pasting from the browser:
$ gh pr checkout https://github.com/adamchainz/home/pull/719
The command creates a local branch with the same name as the remote branch.
This command can check out closed pull requests as well as open ones, even if their branch has been deleted. This is possible because GitHub never deletes the commits that constitute a pull request, even if the branch is deleted.
Interactive mode
If you don’t provide a number or URL, the command will open an interactive prompt to select among open pull requests:
$ gh pr checkout
? Select a pull request [Use arrows to move, type to filter]
> 721 OPEN Plant a lovely bunch of Daffodils [plant-daffodils]
719 OPEN Add a washing machine [washing_machine]
...
Highlight the pull request you want to check out, then press Enter to check it out.
Push back
The command configures the local branch to track the pull request’s branch. This means you can push changes back to the pull request, if you have permission, with a regular git push.
If you rebase the branch, you may find git push fails:
$ git push
To https://github.com/adamchainz/home.git
! [rejected] washing_machine -> washing_machine (non-fast-forward)
error: failed to push some refs to 'https://github.com/adamchainz/home.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Typically, I would advise using Git’s “safe force push” options (--force-with-lease and --force-if-includes) to push changes back to the pull request (per this blog post). These options work in most cases, but for branches from other users’ forks, you’ll need to use the less-safe --force option. Be careful not to overwrite changes you may have missed!
I find gh pr checkout incredibly useful for reviewing pull requests, especially in open source projects. It makes it much easier to close the loop on review and push feedback directly, rather than waiting for the author to make changes.
7. Merge a pull request with gh pr merge
Run gh pr merge to merge a pull request, given its number or URL:
$ gh pr merge 725
...
Run the command without any arguments to merge the pull request associated with the current branch, if one exists:
$ gh pr merge
I find merging pull requests from the command line to be rather satisfying, as it also updates the local Git repository.
Similar to gh pr create, this command has interactive and direct modes, which we’ll now look at.
Interactive mode
By default, gh pr merge uses a series of interactive prompts to decide how to merge the pull request. The first prompt asks you to select a merge method:
$ gh pr merge
Merging pull request adamchainz/home#725 (Clean and reseal the deck)
? What merge method would you like to use? [Use arrows to move, type to filter]
> Create a merge commit
Rebase and merge
Squash and merge
This prompt appears even if the repository has only one merge method enabled. In that case, just press Enter to accept the only option.
The next prompt asks whether to delete the branch locally and on GitHub:
? What merge method would you like to use? Squash and merge
? Delete the branch locally and on GitHub? (y/N)
I would always answer “y” for “yes” here to keep your repository clean.
The next prompt presents three ways forward, plus a cancel option:
? Delete the branch locally and on GitHub? Yes
? What's next? [Use arrows to move, type to filter]
> Submit
Edit commit subject
Edit commit message
Cancel
Asides from Cancel, the options are:
Submit
This option merges the pull request, pulls the base branch locally, and deletes the head branch if you opted to do so:
? What's next? Submit ✓ Squashed and merged pull request adamchainz/home#725 (Clean and reseal the deck) remote: Enumerating objects: 1, done. remote: Counting objects: 100% (1/1), done. remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0) Unpacking objects: 100% (1/1), 842 bytes | 421.00 KiB/s, done. From https://github.com/adamchainz/home * branch main -> FETCH_HEAD 0a21d07..7d67688 main -> origin/main Updating 0a21d07..7d67688 Fast-forward ✓ Deleted local branch deck and switched to branch main ✓ Deleted remote branch deckEdit commit subject
This option lets you edit the commit subject, which is the first line of the commit message.
ghopens your editor with a file containing the subject line, like:Clean and reseal the deck (#725)
Edit, save, and close to loop back to the “What's next?” prompt.
Edit commit message
This option should really be called “Edit commit message body,” since it lets you edit only the body of the commit message after the subject line, not the full message.
ghopens your editor with a file containing the commit message body, like:I washed the deck with a pressure washer, let it dry, and applied Ronseal! Fixes #681.
Edit, save, and close to loop back to the “What's next?” prompt.
Direct mode
Use one of these options to set the merge mode, skipping the interactive prompts:
| Option | Merge mode |
|---|---|
-m (--merge) | Create a merge commit |
-r (--rebase) | Rebase and merge |
-s (--squash) | Squash and merge |
Add the -d (--delete-branch) option to also delete the local and remote branches. For example, to merge using the “Squash and merge” method and delete branches in one swift command:
$ gh pr merge 725 -sd
✓ Squashed and merged pull request adamchainz/home#725 (Clean and reseal the deck)
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (1/1), 841 bytes | 420.00 KiB/s, done.
From https://github.com/adamchainz/home
* branch main -> FETCH_HEAD
9916017..4470f95 main -> origin/main
Updating 9916017..4470f95
Fast-forward
✓ Deleted local branch deck and switched to branch main
✓ Deleted remote branch deck
If your repository uses a merge queue, there’s no need to specify a merge mode, as the queue will handle it for you.
Auto merge
If your repository has the auto merge feature enabled, you can add the --auto option to enable auto merging of the pull request. The pull request will then be automatically merged once it has sufficient review approvals and passes all required checks. For example, to auto-squash-merge a pull request and clean up its remote branch:
$ gh pr merge -sd --auto
✓ Pull request adamchainz/home#725 will be automatically merged via squash when all requirements are met
You’ll need to clean up your local branch later, after the pull request has been merged.
Use --disable-auto to back out of auto-merging a pull request:
$ gh pr merge --disable-auto
✓ Auto-merge disabled for pull request adamchainz/home#28
This option comes in handy when you realize that you forgot to do a thing.
8. Watch a GitHub Actions workflow run with gh run watch
Use gh run watch to watch a run until it completes, showing its progress step-by-step. It’s great for a quick view on what’s happening over on GitHub Actions, and possibly chaining to run actions when it completes, such as sending a notification.
gh run watch has two modes:
Without a run ID
When you run the command without a run ID, it prompts you to select among runs for the current commit. With this mode, you can trigger a run, typically by pushing a commit or opening a pull request, and then use
gh run watchto monitor that run as it completes.For example, say you open a new pull request with
gh pr create:$ gh pr create --fill-first ... https://github.com/adamchainz/home/pull/734
You can then run
gh run watchwithout a run ID, and you’ll be prompted to select from workflow runs for the current commit, even if it’s just one:$ gh run watch ? Select a workflow run [Use arrows to move, type to filter] > * test, CI [test] 1s ago
Press Enter to select the run.
With a run ID
Alternatively, you can specify a run ID to watch a specific run:
$ gh run watch 15835284005
This may be useful if you trigger a run manually, such as with
gh run rerun.
Whichever mode you use to start it, gh run watch will take over your terminal as a “full screen” interface that summarizes the run:
Refreshing run status every 3 seconds. Press Ctrl+C to quit.
* test CI adamchainz/home#734 · 15835284005
Triggered via pull_request less than a minute ago
JOBS
* Tests (ID 44637329913)
✓ Set up job
✓ Run actions/checkout@v4
* Run actions/setup-python@v5
* Install dependencies
* Run tests
* Post Run actions/checkout@v4
The steps within each job are denoted by asterisks (*) when pending, and ticks (✓) when completed. If you have multiple jobs, they will all be listed. Per the top line, the screen will update every 3 seconds.
Once the run completes, the command exits, leaving a summary of the run inline in the terminal:
$ gh run watch
? Select a workflow run * test, CI [test] 1s ago
✓ test CI adamchainz/home#734 · 15835284005
Triggered via pull_request less than a minute ago
JOBS
✓ Tests in 28s (ID 44637329913)
✓ Set up job
✓ Run actions/checkout@v4
✓ Run actions/setup-python@v5
✓ Install dependencies
✓ Run tests
✓ Post Run actions/checkout@v4
✓ Post Run actions/setup-python@v5
✓ Post Run actions/checkout@v4
✓ Complete job
✓ Run CI (15835284005) completed with 'success'
Missing runs
Sometimes, when you run gh run watch without a run ID, it may not find any runs to watch:
$ gh run watch
found no in progress runs to watch
If you’re seeing this immediately after the event that should trigger a run, such as pushing a commit, it is typically because the run has not yet started. There’s a noticeable delay before a run starts, so usually it’s best to wait a few seconds and try again.
If the error persists, check the repository’s Actions tab in the web interface to see if the run failed to start. This might be due to things like a mistake in the workflow file, or a transient failure.
Chain with notification commands
Chain gh run watch with a notification command to alert you when the run completes. I love using this technique to keep an eye on CI runs without having to keep a terminal in the foreground.
In most shells (Bash, Zsh, PowerShell, and Fish, at least), you can use the ; operator to run two commands in sequence. That means you can use this template to run gh run watch and then a notification command when it completes:
$ gh run watch; <notification-command>
Replace <notification-command> with a command that sends a notification when the run completes, for example:
$ gh run watch; say 'CI complete'
Note that you’ll still need to select a run with the initial prompt of gh run watch before it starts watching.
Using ;, as opposed to &&, means that the notification command will run even if the command crashes, so you won’t be left waiting for ages in that case.
There are many different notification commands available, depending on your operating system and preferences. Here are some popular options:
macOS text-to-speech
Use say to have your Mac speak a message when the run completes:
say "Run complete"macOS toast notification
Use osascript to run a short AppleScript that opens a toast notification:
osascript -e 'display notification "Run complete" with title "GitHub Actions"'Linux notification
Use notify-send to show a notification on Linux:
notify-send "Run complete"Windows PowerShell toast notification
Install BurntToast and use its
New-BurntToastNotificationcmdlet to show a toast notification on Windows:New-BurntToastNotification -Text "Run complete"
Reduce output height with --compact
For larger workflows, the default output format of gh run watch can be pretty tall, as it lists all the steps in each job. Use the --compact option to reduce the height of the output, showing only one step per job:
$ gh run watch --compact
While jobs are running, the output will show only the current step of each job:
Refreshing run status every 3 seconds. Press Ctrl+C to quit.
* sprocket CI adamchainz/home#734 · 15836569305
Triggered via pull_request less than a minute ago
JOBS
* Tests (ID 44641276500)
* Run tests
And on completion, the output will show just the last successful or failing step of each job:
$ gh run watch
? Select a workflow run * sprocket, CI [test] 2s ago
✓ sprocket CI adamchainz/home#734 · 15836569305
Triggered via pull_request less than a minute ago
JOBS
✓ Tests in 18s (ID 44641276500)
✓ Run CI (15836569305) completed with 'success'
This option is essentially necessary within larger projects, where test workflows may run many jobs with many, many steps.
Fail the command when a run fails with --exit-status
By default, gh run watch exits with a success status code (0) when the run completes, regardless of whether it succeeded or failed. This can be inconvenient if you want to chain it with another command based on success or failure. To exit with a failing status code (non-zero) when the run fails, use the --exit-status option:
$ gh run watch --exit-status
On Bash and Zsh, you can chain with && and || to run different commands based on the exit status of gh run watch. For example, using macOS’ text-to-speech commands to celebrate success or mildly lament failure:
$ gh run watch --exit-status && say 'Tests passed, huzzah!' || say 'Tests failed, oh no!'
Fin
To see more on other gh commands, along with much other GitHub productivity goodness, do check out Boost Your GitHub DX.
May gh serve you well,
—Adam
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
One summary email a week, no spam, I pinky promise.
Related posts: