An extended GitHub CLI. It caches issues, pull requests, and comments to disk to minimise API calls, and provides PR/issue comment operations beyond the standard gh CLI: inline review comments, line-range comments, thread replies, pending reviews, and local comment stashes.
Issue/PR cache lives at ~/.cache/ghx/cache/ (a single cache.db by default, or per-repository JSON with --storage file). Review-comment stashes live at ~/.cache/ghx/stash/<owner>/<repo>/<pr>.
go install github.com/tomzxcode/ghx@mainOr download a pre-built binary from the releases page.
Requires Go 1.26 or later.
make build # produces ./ghx
make install # installs to ~/.local/bin/ghx, no sudo
sudo make install PREFIX=/usr/local # system-wide install to /usr/local/bin/ghxmake install installs under $(PREFIX)/bin. PREFIX defaults to $(HOME)/.local (the standard per-user location on Linux), so a plain make install needs no sudo. Set PREFIX=/usr/local for a system-wide install. make uninstall removes the binary from the same prefix.
Set a GitHub personal access token in your environment:
export GH_TOKEN=ghp_...GITHUB_TOKEN is also supported and checked as a fallback after GH_TOKEN.
Alternatively, if you have the GitHub CLI installed and authenticated, ghx will use it as a fallback (gh auth token).
All commands accept --repo [HOST/]OWNER/REPO. When omitted, the repository is detected from the origin remote of the current directory.
Pre-populate the local cache with all issues and PRs (including comments):
ghx cache
ghx cache --repo cli/cli
ghx cache --cache-duration 120 # record a 2-hour freshness window for reads
ghx cache --if-stale # do nothing while the cache is still fresh
ghx cache --since 2026-09-01 # refresh entries created/updated since a date
ghx cache --type issues # refresh only issues (or prs)
ghx cache --force # force full re-fetchExample output (first-time run):
Caching issues for octocat/hello-world...
Cached 42 issue(s).
Caching pull requests for octocat/hello-world...
Cached 15 pull request(s).
Cache updated. Valid for 60 minute(s).
Example output (subsequent run, delta fetch):
Fetching issues updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 3 issue(s).
Fetching PRs updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 1 pull request(s).
Cache updated. Valid for 60 minute(s).
List and view commands serve from the cache when it is fresh, and fall back to the GitHub API otherwise.
When they fall back, they also write what they fetch into the cache (using the same records ghx cache produces), so browsing incrementally warms the cache.
By default the cache uses an indexed SQLite database (cache.db), which is several times faster than the legacy file backend for listing, filtering, and searching on large repositories. The file backend (one JSON file per issue/PR) is still available:
ghx --storage file cache --repo cli/cli # or set GHX_STORAGE=file
ghx cache migrate # copy an existing file cache into SQLiteAn existing file-based cache is not read by the default backend until it is migrated. cache migrate is non-destructive (the file cache is kept) and idempotent. See the cache guide for details.
# List open issues (default)
ghx issue list
# Filtering
ghx issue list --state all
ghx issue list --state closed
ghx issue list --author alice
ghx issue list --assignee bob
ghx issue list --label bug
ghx issue list --label bug --label p1 # AND logic
ghx issue list --milestone v2.0
ghx issue list --mention carol
ghx issue list --app dependabot
ghx issue list --search "memory leak"
ghx issue list --limit 10
# View a single issue
ghx issue view 42
ghx issue view 42 --comments
ghx issue view 42 --refresh # force fetch from GitHub and update cacheExample output (issue list):
#42 Fix login bug bug, priority 5 2025-01-15
#41 Update README documentation 2 2025-01-14
#40 Very long title that will be truncat… enhancement 0 2025-01-13
Example output (issue view 42 --comments):
#42 Fix login bug
OPEN • opened by octocat • 5 comment(s)
Labels: bug, priority
Assignees: alice
Milestone: v1.0
Created: 2025-01-10 14:30
URL: https://github.com/octocat/hello-world/issues/42
The login page crashes when...
── Comment 1 by reviewer1 (2025-01-12 09:15) ──
I can reproduce this on Firefox.
── Comment 2 by octocat (2025-01-12 10:30) ──
Thanks, I'll look into it.
# List open PRs (default)
ghx pr list
# Filtering
ghx pr list --state all
ghx pr list --state merged
ghx pr list --author alice
ghx pr list --assignee bob
ghx pr list --label enhancement
ghx pr list --base main
ghx pr list --head feat/dark-mode
ghx pr list --draft
ghx pr list --app dependabot
ghx pr list --search "fix crash"
ghx pr list --limit 10
# JSON output, gh-compatible field selection
ghx pr list --state merged --head feat/dark-mode --json number,headRefOid,mergedAt
ghx pr list --json # every supported field
# View a single PR
ghx pr view 10
ghx pr view 10 --comments
ghx pr view 10 --refresh # force fetch from GitHub and update cacheExample output (pr list):
#10 Add new feature feature → main approved 3 2025-01-15
#9 Fix edge case [draft] fix-bug → dev review required 1 2025-01-14
Example output (pr view 10 --comments):
#10 Add new feature
OPEN • opened by octocat • 3 comment(s)
Branch: feature → main
Review: approved
Labels: enhancement
Assignees: alice, bob
Created: 2025-01-08 11:00
URL: https://github.com/octocat/hello-world/pull/10
This PR adds a new feature...
── Comment 1 by reviewer1 (2025-01-09 08:30) ──
Looks good, approved!
── Comment 2 by octocat (2025-01-09 09:00) ──
Thanks for the review.
List locally cached repositories:
ghx repo listExample output:
REPO ISSUES PRS CACHED AGE STATUS ISSUE-LIST ISSUE-VIEW PR-LIST PR-VIEW
octocat/hello-world 42 15 2h30m fresh 92% (13) 80% (5) 75% (8) 60% (5)
alice/another-repo 10 3 1d stale — — — —
The four hit-rate columns show how often each retrieval operation was served from the local cache instead of calling GitHub, with the total number of recorded retrievals in parentheses. They come from the local telemetry database and are split by operation: issue list, issue view, PR list, and PR view. A dash means no retrieval of that kind has been recorded for the repository, for example when telemetry is disabled or was cleared.
Delete the cached data for one repository:
ghx repo delete octocat/hello-worldBrowse the cache in a GitHub-like web UI (repo switcher, issue/PR lists and details, Ctrl+P search palette):
ghx cache && ghx serve
ghx serve --host 127.0.0.1 --port 9000 --openRun a local JSON-RPC 2.0 server so clients can call the CLI without spawning a process per call:
ghx rpc serve # POST /rpc on 127.0.0.1:8090
ghx rpc methods # list methods
ghx rpc call repo.list
ghx rpc call issue.list '{"repo":"cli/cli","state":"open","limit":20}'
ghx rpc call pr.view '{"repo":"cli/cli","number":1234}'Typed methods cover the read and cache commands (repo.list, repo.delete, issue.list, issue.view, pr.list, pr.view, pr.threads, cache.refresh).
The cli.run method executes any other ghx command and returns its stdout, stderr, and exit code:
ghx rpc call cli.run '{"args":["pr","comment","10","--body","Looks good"]}'
ghx rpc exec -- pr comment 10 --body "Looks good"Any command can run against a server with --remote:
ghx --remote http://127.0.0.1:8090 issue list --state openghx serve also exposes the API at /rpc on its web UI port.
See the JSON-RPC guide for the method reference, authentication, and security notes.
# Top-level comment
ghx pr comment 10 --body "Looks good"
# Inline comment on a line or range
ghx pr comment 10 --file src/main.go --line 42 --body "Nit"
ghx pr comment 10 --file src/main.go --line 42-45 --body "Consider extracting"
# File-level comment (no --line)
ghx pr comment 10 --file src/main.go --body "Overall looks clean"
# Reply to an existing review thread
ghx pr comment 10 --reply-thread <thread-id> --body "Agreed"
# Add to a pending review instead of posting immediately
ghx pr comment 10 --file src/main.go --line 42 --body "Nit" --pending
# Save to a local stash entry instead of calling the API
ghx pr comment 10 --file src/main.go --line 42 --body "Nit" --stash
# Body from stdin or a file
ghx pr comment 10 --body-file -
ghx pr comment 10 --body-file comment.txtghx pr comment edit <comment-id> --body "Updated text"
ghx pr comment delete <comment-id>
ghx issue comment edit <comment-id> --body "Updated text"
ghx issue comment delete <comment-id>Use ghx pr threads <number> --ids or ghx issue view <number> --ids to find comment IDs.
ghx pr threads 10 # open threads, with comments
ghx pr threads 10 --thread <thread-id> # show one thread
ghx pr threads 10 --ids # include comment IDs
ghx pr threads 10 --state all
ghx pr threads 10 --state resolvedghx pr review create 10
ghx pr review list 10
ghx pr review submit 10 --event COMMENT
ghx pr review submit 10 --event APPROVE --body "LGTM"
ghx pr review submit 10 --event REQUEST_CHANGES --body "See comments"
ghx pr review discard <review-id>ghx pr review stash push 10 -m "nit comments" # save pending -> stash@{0}
ghx pr review stash list 10 # list stash entries
ghx pr review stash pop 10 # restore stash@{0} into a pending review
ghx pr review stash pop 10 --stash 1 # restore stash@{1}
ghx pr review stash drop 10 # discard stash@{0}ghx issue comment 42 --body "Fixed in #50"
ghx issue comment 42 --body-file -
ghx issue view 42 --ids # show comment IDsGenerate an HTML report of pull request activity (lead times, review engagement, PR sizes, trends) for one or more repositories:
ghx cache # populate the cache first
ghx stats --from 2026-05-01 --to 2026-05-31 # current repository
ghx stats acme/widget acme/gadget --from 2026-05-01 --to 2026-05-31 \
--author alice,carol --reviewer alice,bob -o may-report.htmlThe report includes summary cards, a monthly trend chart, an author × reviewer
matrix, per-author lead times (time to merge, time in draft, time to first
review, time to approve, time from approval to merge), contribution and PR size
breakdowns, reviewer
engagement with review-request response times, a PR size versus merge time
table, peak-activity and size-distribution charts, notable PR lists, and an
optional appendix of individual PRs (--list-prs).
Run ghx cache --force once after upgrading so reports can use the review,
timeline and size data collected during caching.
Record per-call API and cache timings locally, then inspect or export them:
ghx cache --repo cli/cli # recording is on by default
ghx telemetry summary --repo cli/cli # counts, p50/p95, retry rate by operation kind
ghx telemetry export --kind pr_full_page -o pages.json
ghx telemetry disable # turn recording off globally
ghx telemetry enable # turn it back on
ghx telemetry status # show the effective state
ghx telemetry clear --before 2026-09-01 # delete old eventsRecords are local-only and never uploaded. Turn recording off globally with
ghx telemetry disable, or for one run with --telemetry=false
(GHX_TELEMETRY=0); --telemetry-db / GHX_TELEMETRY_DB override the database
path. A failure to record never fails or slows a command. The mock server can
inject latency fitted from recorded events, so generated scenarios reproduce
measured cost:
ghx mock serve --preset default --latency-from ~/.local/share/ghx/telemetry.dbscripts/benchmark.py times equivalent read commands from ghx and gh
across warm-cache, cold-cache and forced-refresh scenarios, then writes a
self-contained HTML report:
python3 scripts/benchmark.py --repo cli/cli # benchmark a specific repo
python3 scripts/benchmark.py --runs 7 -o report.html # more samples, custom output
python3 scripts/benchmark.py --demo # preview the report offlineThe ghx side runs against the backend selected by --storage (sqlite, the
default, or file). Run the script once per backend to compare them:
python3 scripts/benchmark.py --repo cli/cli --storage sqlite -o sqlite.html
python3 scripts/benchmark.py --repo cli/cli --storage file -o file.htmlOr compare the two backends head-to-head in a single report with
--compare-backends, which runs ghx with each backend and shows them side by
side (it drops the gh comparison):
python3 scripts/benchmark.py --repo cli/cli --compare-backends -o backends.htmlOn small repositories the file backend can win warm-cache reads because there is little to index; SQLite's advantage grows with the number of cached items.
It is standard-library-only, and ghx runs against a throwaway --cache-dir, so
the real ~/.cache/ghx cache is never touched. The report lands in
benchmark-report.html by default.
Because the tools do not always do the same work (ghx serves from cache; gh fetches project items and a richer field set), every scenario carries a fairness badge and note ("Different work", "ghx does more", "ghx does less") so a speedup is not read as a like-for-like win.
| Command | Cache behaviour |
|---|---|
cache |
Fetches everything (all states, with comments) and writes it to the selected backend (SQLite by default, or one JSON file per issue/PR for the file backend). Each run delta-refreshes from the last cache write; --if-stale skips the run while the cache is fresh. Supports --since windowed refresh and --type issues/prs partial refresh. |
issue list / pr list |
Reads the cache and applies filters when it is fresh (indexed SQL predicates for SQLite); falls back to the GitHub API with server-side filters otherwise. When it falls back, each entry not already cached is fetched in full (comments, reviews, timeline) and written to the cache. |
issue view / pr view |
Serves the individual cached item when it is less than 60 minutes old, or the full cache is fresh; fetches from the API and updates the cache otherwise. An item missing from an otherwise-fresh cache is fetched and cached rather than reported as missing. When the number is cached under the other type (a pull request viewed as an issue, or the reverse), it reports that type and exits without querying the API. --refresh bypasses the same-type cache checks. |
Run a read command with --log-level debug to see where its time goes (repository resolution, cache store open, token resolution, the list call, and the cache backfill stages).
Repository coordinates are matched case-insensitively (as GitHub does) and stored lower-case, so a repository has one cache entry however its name is spelled.
Every command and flag is listed in the generated user guide flag reference; run ghx <command> --help for the authoritative list from the binary in front of you.