gh-inbox: clean up GitHub notifications in bulk from the command line
Summary
gh-inbox is a GitHub CLI extension that lets developers bulk-clean their notifications inbox from the command line, marking notifications as done, read, or unsubscribing by repository, type, reason, age, or state.
View Cached Full Text
Cached at: 10/30/26, 03:19 AM
maraino/gh-inbox
Source: https://github.com/maraino/gh-inbox
gh-inbox
A GitHub CLI extension that cleans up your notifications inbox: mark notifications as done or read, or unsubscribe from them, in bulk, by repository, type, reason, age, state, or the people involved.

$ gh inbox clean --state merged,closed
Would mark 1835 notifications as done.
1835 notifications
By repository
321 acme/gitops
132 acme/agent
…
Run again with --apply to do it, or add --list to see each notification.
$ gh inbox clean --state merged,closed --apply
Marked 1835 notifications as done.
Install
gh extension install maraino/gh-inbox
Or, with Go 1.26.7 or later:
go install github.com/maraino/gh-inbox@latest
go install puts a gh-inbox binary in your GOBIN. Run it directly as
gh-inbox, or link the extension so gh inbox works too:
git clone https://github.com/maraino/gh-inbox && cd gh-inbox && gh extension install .
gh-inbox uses your gh login. Your token needs the notifications or repo
scope, and read:org for filters on review requests that name a team or
@me.
GitHub Enterprise Server
Log in to the host with gh, then pass it with --hostname, or set
GH_HOST:
gh auth login --hostname github.example.com
gh inbox --hostname github.example.com list --summary
gh can also take the token from GH_ENTERPRISE_TOKEN. The record of
notifications marked as done is kept per host.
Usage
gh inbox list [filters | --rule name[,name] | --rules] [--sort key[,key]] [--reverse] [--limit n] [--json | --summary]
gh inbox clean [filters] [--action done|read|unsubscribe] [--apply] [--all] [--list [--sort key[,key]] [--reverse]]
gh inbox clean --rule name[,name] | --rules [--rules-file file] [--apply] [--list [--sort key[,key]] [--reverse]]
gh inbox triage [filters | --rule name[,name] | --rules] [--sort key[,key]] [--reverse]
gh inbox unwatch [owner/name...] [--apply]
list shows the notifications that match the filters, most recently updated
first. clean acts on them: it only shows what it would do unless you pass
--apply, and it refuses to act on every notification unless you pass --all.
It shows a summary of the notifications it acts on or, with --list, each
one, as list does.
triage goes through them one at a time: press o to open one in the
browser, d to mark it as done, r to mark it as read, u to unsubscribe,
s to skip it, p to go back to the previous one, or q to quit. Keys
pressed while a request runs are ignored, as are pasted text and keys with
Alt, so a stray key can’t act on the next notification.
unwatch stops watching repositories, which is where the notifications with
reason subscribed come from. Without arguments, it lists those repositories,
most notifications first; with names or * patterns, it shows which ones it
would stop watching, and does it with --apply. Threads you take part in or
are mentioned in still notify you, and the notifications already in the inbox
stay until you clean them.
list, triage and clean --list show the most recently updated first.
--sort orders them by updated (newest first), repo, type, reason or
state (open, draft, merged, closed, then unknown) instead. With several
keys, as in --sort repo,state, later keys break ties, and the most recently
updated comes first among the rest. --reverse reverses the order.
Filters
| Flag | Matches |
|---|---|
--repo | Repositories, as owner/name, with * wildcards |
--type | pr, issue, release, ci, discussion, advisory, dependabot, commit |
--reason | Why GitHub notified you: review_requested, mention, subscribed, ci_activity, … |
--state | open, closed, merged |
--unread, --read | Read status |
--older-than | No activity for a while: 36h, 7d, 2w |
--newer-than | Activity recently: 24h, 7d; with --older-than, a window |
--draft, --not-draft | Draft pull requests, or pull requests that aren’t drafts and issues |
--label, --not-label | Labels of the pull request or issue, ignoring case |
--author, --not-author | Who opened the pull request or issue |
--assigned-to, --not-assigned-to | The assignees |
--review-requested, --no-review-requested | Users or org/teams whose review is requested |
--involves, --not-involves | Assignees or requested reviewers |
- A filter with several values, comma-separated or repeated, matches when any value matches. A negated filter matches when none does. A notification must match every filter.
- Filters on people take a login or
@me. Filters on review requests also takeorg/team, and for them@meincludes your teams. - GitHub drops a review request once the review is submitted, so
--review-requested @mematches the reviews you still owe, while--reason review_requestedmatches every notification sent because your review was requested. - Filters on the state or people of a pull request or issue don’t match other notifications, such as CI runs. Pull requests and issues that can’t be read anymore are skipped and listed.
Rules
Cleanups you repeat can be saved as rules in ~/.config/gh-inbox/rules.yml
(or $XDG_CONFIG_HOME/gh-inbox/rules.yml). Each rule has a name, an action
(done unless given), and filters with the same names and values as the
flags:
rules:
- name: merged
filters:
state: [merged, closed]
- name: stale-ci
action: read
filters:
type: ci
older-than: 7d
- name: noisy-repos
action: unsubscribe
filters:
repo: ["acme/infra", "acme/docs-*"]
reason: subscribed
examples/rules.yml is a starting point to copy.
gh inbox clean --rules applies every rule, and --rule merged,stale-ci only
those named. As with filters, nothing happens without --apply. gh inbox list and gh inbox triage take --rule and --rules too, to show or go
through each notification the rules would act on.
Rules apply in the order of the file, and a notification only goes to the first rule that matches it. A rule must have filters, and a mistake in any rule stops them all before anything is done.
Examples
# What's in the inbox, by repository, type, reason and state
gh inbox list --summary
# Pull requests waiting for your review, or your teams'
gh inbox list --state open --review-requested @me
# Go through the reviews you owe one at a time
gh inbox triage --state open --review-requested @me
# What happened in the last day
gh inbox list --newer-than 24h
# Repositories you watch that fill the inbox, and stop watching one
gh inbox unwatch
gh inbox unwatch acme/infra --apply
# Draft pull requests labeled dependencies
gh inbox list --draft --label dependencies
# Mark merged and closed pull requests and issues as done
gh inbox clean --state merged,closed --apply
# Apply every saved rule, for example from cron
gh inbox clean --rules --apply
# Open pull requests that are neither assigned to you nor waiting for your review
gh inbox clean --type pr --state open --not-involves @me --apply
# Mark CI notifications older than a week as read
gh inbox clean --type ci --older-than 7d --action read --apply
# Unsubscribe from threads you only follow because you watch the repository
gh inbox clean --repo 'acme/*' --reason subscribed --action unsubscribe --apply
Done notifications
Marking a notification as done removes it from the inbox. It stays under the “Done” filter on github.com, and comes back when there’s new activity.
The API keeps listing done notifications, and can’t tell them apart from read
ones. So gh-inbox keeps a record of the notifications it marked as done, in
~/.local/state/gh-inbox/<host>/done.json (or under $XDG_STATE_HOME), and
skips them until they have new activity. Notifications marked as done
elsewhere, such as on github.com, aren’t in the record, and clean marks them
as done again, which is harmless. --no-cache ignores the record.
Cache
To make running several commands in a row fast, gh-inbox caches API responses
in ~/.cache/gh-inbox/<host> (or under $XDG_CACHE_HOME):
- The list of notifications is reused for up to 10 minutes while GitHub says there’s nothing new, which it answers without counting against the rate limit. Marking a notification as done or read, or unsubscribing, with gh-inbox starts a new list. A notification read on github.com can show as unread until the list is renewed.
- The details of a pull request or issue, such as its state and labels, are reused for up to an hour while its notification has no new activity. Some changes, such as new labels, don’t count as activity.
--no-cache ignores the cache, as well as the record of done notifications;
the responses it gets still renew the cache.
Other flags
| Flag | Meaning |
|---|---|
--hostname | The GitHub host, for GitHub Enterprise Server. Defaults to the gh default host. |
--concurrency | How many requests clean makes at the same time (default 4) |
--verbose | Log API requests and retries to stderr |
Requests that hit a rate limit or a transient server error are retried.
Development
make # lint, test and build
make test # tests, with the race detector; RACE= runs them without it
make lint # golangci-lint
make fmt # format the code
make govulncheck
make install # build and install the working tree as the gh extension
make demo # record demo/demo.gif; needs Python 3 with Pillow
golangci-lint and govulncheck are Go tool dependencies, so go tool runs the
versions pinned in go.mod.
The code is in internal/:
| Package | Contents |
|---|---|
cmd | The commands and their flags |
github | The GitHub API client |
inbox | The notification model |
filter | The filters |
action | Applying an action to many notifications at once |
cache | The record of notifications marked as done |
render | Table, summary and JSON output |
demo/ records the GIF above. demo/fakeapi serves a made-up inbox for the
host github.localhost, capture.py runs the commands against it in a
pseudo-terminal, and render.py draws the frames.
License
Similar Articles
@github: Enable all of these free security settings in public repos with just one command. Get the gh CLI extension gh secure ht…
The article introduces 'gh secure', a GitHub CLI extension that enables free security settings in public repositories with a single command, following best practices from the GitHub Security Lab.
@github: 20,000+ secret scanning alerts across 15,000 repos. Nine months later, GitHub reached inbox zero.
GitHub achieved inbox zero for over 20,000 secret scanning alerts across 15,000 repositories after nine months of effort.
@github: The 5 commands to use in GitHub CLI (token-free) 👀
This tweet links to an article detailing five commands for GitHub CLI that work without authentication tokens, aimed at developers using GitHub's command-line interface.
@github: New updates in GitHub Issues • Pin views in the Issues sidebar • See profile avatars for reactions • Adjust dashboard d…
GitHub has announced new updates to its Issues feature, including pinning views in the sidebar, profile avatars for reactions, adjustable dashboard density, hiding closed sub-issues, and a scope-aware Issue dependency REST API.
@github: Sometimes it's easier to show than tell. We're sure this update will help with that. GitHub CLI now has a repeatable --…
GitHub CLI now supports a repeatable --attach flag for uploading images and videos directly in issues, pull requests, and comments, enhancing developer workflows by allowing inline media references.