Skip to content

Cache

ghx caches GitHub data locally (by default in a single SQLite database) and serves results from cache without hitting the API after an initial fetch.

Cache location

The default SQLite backend stores everything in one database:

~/.cache/ghx/cache/cache.db

The legacy file backend stores one JSON file per item:

~/.cache/ghx/cache/<host>/<owner>/<repo>/
├── .cache_info.json
├── issues/
│   ├── 1.json
│   ├── 2.json
│   └── ...
└── prs/
    ├── 1.json
    ├── 2.json
    └── ...

Override the base directory with --cache-dir:

ghx --cache-dir /tmp/gh-cache cache --repo cli/cli

Storage backends

ghx defaults to an indexed SQLite database and also supports the legacy file backend:

Backend Layout Select
sqlite (default) A single SQLite database at <cache-dir>/cache.db with indexed lookups --storage sqlite or GHX_STORAGE=sqlite
file One JSON file per issue/PR under <cache-dir>/<host>/<owner>/<repo>/ --storage file or GHX_STORAGE=file

Select the file backend per invocation with --storage, or set GHX_STORAGE in your environment:

ghx --storage file issue list --repo cli/cli --state all
GHX_STORAGE=file ghx pr list --repo cli/cli

--storage takes precedence over GHX_STORAGE. Both backends are keyed by the same repository coordinates, so commands and filters behave identically; only the on-disk representation and query performance differ. On large repositories (hundreds to thousands of cached items), listing, filtering, and searching against SQLite is several times faster because it avoids reading and parsing every item file.

Migrate an existing file cache to SQLite

The default SQLite backend does not read the file cache. If you have an existing file cache, import it once:

ghx cache migrate                 # migrate every cached repository
ghx cache migrate --repo cli/cli  # migrate a single repository

The file cache is left in place (the migration is non-destructive), and re-running it is safe (idempotent). To keep using the file cache without migrating, pass --storage file.

Populate the cache

ghx cache
ghx cache --repo cli/cli
ghx cache --cache-duration 120   # treat cache as fresh for 2 hours
ghx cache --cache-duration 0     # always re-fetch (delta)
ghx cache --force                # force full re-fetch

Refresh one portion only with --type

--type issues or --type prs refreshes only that portion; --type both (the default) refreshes both. Like --since, an explicit --type bypasses the freshness short-circuit:

ghx cache --type issues
ghx cache --type prs
ghx cache --type prs --since 2026-09-01   # combine with a date window
ghx cache --type issues --force           # full issue re-fetch only

Notes:

  • A partial run never marks an incomplete cache complete; run a plain ghx cache (or --force) to finish fetching the missing portion.
  • --force with a partial --type resets only that portion's resume cursor.

Refresh a specific window with --since

--since refreshes only entries created or updated on or after a given date, instead of the default last-cache-write delta:

ghx cache --since 2026-09-01
ghx cache --since 2026-09-01T15:04:05Z
ghx cache --since "2026-09-01 15:04"

Accepted formats: YYYY-MM-DD (UTC midnight), RFC3339, and naive datetimes (local time).

Notes:

  • Issues use a server-side filter with exact timestamps. PRs are fetched in two phases: a lightweight newest-first walk locates the exact window (so the progress bar knows the total), then the window is fetched in small full-payload pages. Both avoid the search API.
  • --since bypasses the freshness short-circuit and cannot be combined with --force.
  • Using a --since date newer than the last cache write intentionally skips items in between; run ghx cache --force afterwards if you need to backfill them.

First run fetches everything. Example output:

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).

Subsequent runs use a delta fetch, only retrieving items updated since the last cache write:

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).

How freshness works

The cache metadata (the .cache_info.json file for the file backend, the cache_meta table for SQLite) tracks when the cache was last written and the configured duration. The file-backend form:

{
  "cachedAt": "2025-01-15T10:30:00Z",
  "duration": 60
}

A cache is considered fresh when time.Since(cachedAt) < duration × 1 minute.

When the cache is fresh, list and view commands serve entirely from the cache with no API calls. When stale, they fall back to the GitHub API.

Cache behavior per command

Command Behavior
cache Fetches all issues and PRs (all states, with comments). Skips if cache is younger than --cache-duration. Supports delta fetch, --since windowed refresh, and --type issues/prs partial refresh.
issue list / pr list Reads the cache and applies filters when the cache is fresh (indexed SQL predicates for the SQLite backend). Falls back to the GitHub API when stale. Does not write to cache.
issue view / pr view Serves the individual cached item if the full cache is fresh, or if it was written less than 60 minutes ago. Otherwise fetches from the API and saves to cache. --refresh bypasses all checks.

Notes

  • Transient failures (rate limits and GitHub 5xx errors such as an HTML 502 from a proxy) are retried automatically with exponential backoff. Each fetched page is already persisted, so if a run still fails, re-running resumes from the last page written.
  • Cache data is never automatically cleaned up. Delete ~/.cache/ghx/cache/cache.db (SQLite) or the per-repository directories under ~/.cache/ghx/cache/ (file) to free space.
  • The --mention and --app filters cannot be evaluated from cached data and are silently skipped when serving from cache.
  • Bulk cache operations fetch up to 100 comments per item.