An extended GitHub CLI. It caches issues, pull requests, and comments to disk to minimise API calls, searches that cache directly, 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.
The search commands are the exception: they search every cached repository unless --repo is given.
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 # ensure coverage starts at a date
ghx cache --type issues # refresh only issues (or prs)
ghx cache --force # delete the selected type and start over
ghx cache delete 42 # delete one cached issue or PRExample 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, catch-up):
Fetching issues updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 3 issue(s).
Fetching pull requests updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 0 pull request(s).
Cache updated. Valid for 60 minute(s).
Coverage is recorded as merged updatedAt ranges per type, so an interrupted backfill resumes the unfinished segment and a later run reads only the uncovered ranges. --workers tunes the parallel segment pass; the width is chosen from the uncovered duration and full pull request pages are capped at two in flight.
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 cache
# List the individual checks behind a PR's head-commit rollup
ghx pr checks 10
ghx pr checks 10 --jsonExample 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
Checks: failing
Threads: 2 open of 4
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.
Pass --json for the same data as a JSON array, optionally limited to a comma-separated list of fields (see the repositories guide):
ghx repo list --json nameWithOwner,issueCount,prCount,statusDelete the cached data for one repository:
ghx repo delete octocat/hello-worldSearch the local cache across every cached repository, with no API calls:
ghx search issues login # every cached repo
ghx search issues "memory leak" # phrase match
ghx search issues login safari # both terms must match
ghx search prs crash --state merged
ghx search prs --draft
ghx search issues --assignee @me
ghx search issues --no-assignee --created '>=2026-01-01'
ghx search issues --include-prs --sort comments
ghx search prs --review-requested @me --checks failure
ghx search issues login --repo octocat/hello-world --label bug
ghx search prs --json=number,title,state,repository
ghx search issues login --template '{{.repository.nameWithOwner}}#{{.number}}'Example output:
octocat/hello-world#42 Fix login bug open 2026-10-30
octocat/hello-world#41 Update README closed 2026-10-29
search reads only the local cache, so run ghx cache first.
Query words are matched case-insensitively against title, body, and comments, and every word must match; --match restricts the fields.
Quoted arguments stay phrases, --state defaults to all, and --limit defaults to 30 (0 for no limit).
Filters cover author, assignee, mention, involvement, labels, milestones, missing values, dates, comment counts, owners, merge state, checks, reviews, and requested reviewers.
--sort, --order, --json=fields, and --template control ordering and output.
@me works on the user filters and resolves from gh's local config without an API call.
--review-requested @me also matches teams you belong to, and --mentions @me and --involves @me also match those teams' mentions; ghx cache records team memberships per host.
--review-requested, --mentions, and --involves also accept an explicit team handle as @org/team or org/team.
The gh search flags that need repository metadata, reactions, projects, or server-side ranking are not available.
See the search guide.
Browse the cache in a GitHub-like web UI (repo switcher, issue/PR lists and details, Ctrl+P search palette, stats report at /stats, process report at /process):
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 resolved
ghx pr threads resolve <thread-id> # mark a thread as resolved
ghx pr threads resolve <id1> <id2> # resolve several threads
ghx pr threads unresolve <thread-id> # reopen a resolved thread
ghx pr threads unresolve <id1> <id2> # reopen several threadsghx 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.
Generate an HTML production-line report of issue and PR flow for one or more repositories:
ghx cache # populate the cache first
ghx process # current repository, last 90 days
ghx process acme/widget --days 30 -o flow-report.htmlThe report reads issues and pull requests as two production lines: open issues
flow to closed, and pull requests flow from draft to ready to merged (or
discarded). It shows each line's station inventory with queue ages, a
cumulative flow diagram, cycle-time distributions with histograms, weekly
arrivals versus completions, Little's law (work in process, throughput, and
the implied cycle time), a Monte Carlo forecast of when the current items
clear, queue-pressure trends, and the aging list of everything still on the
line. The same report is served by the web UI at /process, and the numbers
are available as JSON at /api/process (see the serve guide).
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 fills the uncovered updatedAt ranges; --if-stale skips the run while the cache is fresh. Supports --since, --workers, and --type issues/prs. |
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 was written within the cache's configured duration (60 minutes by default), 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. |
search issues / search prs |
Reads only the local cache, across every cached repository unless --repo limits it to one. Never calls the API and never refreshes; the results are as current as the cache. |
Run a read command with --log-level debug (or GHX_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.
- gitcrawl is a local-first GitHub issue and pull request crawler for maintainer triage.
- brunoborges/ghx is an unrelated project that reuses the
ghxname: a caching daemon that proxies theghCLI to avoid API rate limits, rather than a command-line tool with its own commands.