gh issue list fails with stale rate-limit headers from cached introspection response
- Dominant language
- Go
- Stars
- 46.3k
- Forks
- 9k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 89
Description
### Describe the bug
After a GraphQL rate limit is exhausted and then resets, `gh issue list` (and other subcommands using GraphQL) continues to fail with "API rate limit exceeded" for up to 24 hours. Meanwhile, `gh api graphql` works fine with the same token.
The root cause: `gh` caches a `SearchType` introspection query with `X-Gh-Cache-Ttl: 24h`. When this query is made during rate limit exhaustion, the cached **response** carries `X-Ratelimit-Remaining: 0` in its headers. After the actual rate limit resets (~1 hour), `gh` serves this response from cache and reads the stale rate-limit headers, refusing to make new requests.
### Steps to reproduce the behavior
1. Exhaust the GraphQL rate limit (e.g. via automated tooling making many `gh issue list` calls)
2. Wait for the rate limit to reset (~1 hour)
3. Verify the rate limit has reset:
```
$ gh api rate_limit --jq '.resources.graphql'
{"limit":5000,"remaining":4148,"reset":1772334629,"resource":"graphql","used":852}
```
4. Try `gh issue list`:
```
$ gh issue list --repo owner/repo --limit 1 --json number
GraphQL: API rate limit already exceeded for user ID 477956.
```
5. Meanwhile, direct GraphQL works fine:
```
$ gh api graphql -f query='{ viewer { login } rateLimit { remaining } }'
{"data":{"viewer":{"login":"kim-em"},"rateLimit":{"remaining":4136}}}
```
### Evidence from `GH_DEBUG=api`
```
* Request at 2026-03-01 12:15:24 +0000 UTC
* Request to https://api.github.com/graphql
> X-Gh-Cache-Ttl: 24h0m0s <-- request asks for 24h cache
< HTTP/2.0 200 OK
< Date: Sun, 01 Mar 2026 01:42:54 GMT <-- response is ~10h old (from cache)
< X-Ratelimit-Remaining: 0 <-- stale! actual remaining is 4148
< X-Ratelimit-Reset: 1772331384
< X-Ratelimit-Used: 5001
```
The response `Date` header (`01:42:54`) is from when the rate limit was exhausted. The request timestamp (`12:15:24`) is current. `gh` is serving the cached response and honoring its stale rate-limit headers.
### Root cause
The `SearchType` introspection query (used internally by `gh issue list` and similar subcommands) is cached with a 24-hour TTL. The rate-limit headers are properties of the request moment, not the response content. When gh serves a cached response, it should not use the cached rate-limit headers to gate new requests.
### Workaround
Delete the cached introspection response:
```sh
# Find and remove cached responses with exhausted rate limits
grep -rl "X-Ratelimit-Remaining: 0" ~/.cache/gh/ 2>/dev/null | xargs rm -f
```
### Expected vs actual behavior
**Expected:** After the rate limit resets, `gh issue list` should work again.
**Actual:** `gh issue list` fails for up to 24 hours after rate limit recovery due to stale cached headers.
### Suggested fix
Either:
1. Strip rate-limit headers when storing responses in cache, or
2. Don't check rate-limit headers when serving from cache, or
3. Include rate-limit state in cache invalidation (evict entries whose `X-Ratelimit-Reset` timestamp has passed)
### gh version
```
gh version 2.83.2 (nixpkgs)
```
### Note
This is likely the root cause of many reports in #8321, where users see "rate limit exceeded" while `gh api /rate_limit` shows plenty of remaining quota. That issue was closed without identifying the caching mechanism.
Contributor guide
Assessment
This issue has not been assessed yet.