Desktop app's GhRestClient burns the user's GitHub GraphQL rate limit: ~640 points per session focus, ~2,000 per turn start
Summary
The Claude Code desktop app (macOS) makes very large bursts of GitHub GraphQL API calls through its internal GhRestClient, authenticated as the user, and exhausts the user's personal 5,000 points/hr GraphQL rate limit within minutes of normal use. Measured costs:
- ~640 points for a single session focus in the sidebar (
setFocusedSession/ preview warm-up) - ~1,970 points for sending one message / starting a turn
- windows repeatedly reach 10,000+ used (2× the limit, indicating high parallelism)
Because GitHub's GraphQL limit is per-user across all tokens, this starves everything else that shares the identity: gh CLI in sessions (a plain gh pr checks costs 1 point), the GitHub MCP server, and the app's own CI widgets — sessions then see "GitHub API rate limit exceeded (5,000/hr shared across all tools and agents)" and CI watchers fail silently. Likely related symptom: #78222.
Environment
- Claude desktop app 1.32885.1 (macOS, Darwin 25.5.0), Claude Code CLI 2.1.234
- ~30 local sessions in one project over recent days; the app has ~38 session↔PR bindings across 5 repos (log shows 648
LocalSessions.getPrChecksentries,prNumber,dismissBoundPr)
Evidence
~/Library/Logs/Claude/main.log — the app's own GraphQL client hitting the wall repeatedly since Aug 18:
2026-08-18 17:55:29 [warn] [GhRestClient] GraphQL errors { '0': { type: 'RATE_LIMIT', code: 'graphql_rate_limit', message: 'API rate limit already exceeded for user ID 3220689.' } }
2026-08-18 19:57:11 [warn] [GhRestClient] GraphQL errors { ... }
2026-08-19 21:42:12 [warn] [GhRestClient] GraphQL errors { ... }
2026-08-20 08:30:14 [warn] [GhRestClient] GraphQL errors { ... }
Controlled measurement (sampling gh api rate_limit --jq .resources.graphql.used every 10–15s, correlated with main.log timestamps and lsof snapshots showing only the Claude process holding GitHub TCP connections at spike time):
- Fresh window at 12:47:41 →
used=0 - 12:49:02 log:
setFocusedSessionon an idle session → 12:49:12: +640 points - 12:49:26 log:
sendMessageto that session → 12:50:59: +1,970 points (plus +310 at 12:51:15 as the turn spun up) - With all sessions idle and no UI interaction, usage is flat (~2 points/min) — the burst is entirely UI/turn-event driven
Ruled out by measurement during the same investigation: user shell commands (each gh pr checks/view/list = 1 point), SessionStart hooks (0 points), CI workflows (repo-scoped tokens; no runs during bursts), cron/launchd jobs, OAuth apps (GitHub settings show only GitHub CLI recently used), and the cloud "Autofix pull requests" feature (bursts continue with it disabled and correlate with the local process).
What did NOT mitigate it
- Archiving finished PR-bound sessions (burst quantum unchanged at ~640 → fan-out appears not proportional to active session count)
- Disabling claude.ai → Settings → Claude Code → "Autofix pull requests"
Expected
Session focus / turn start should cost O(1–10) GraphQL points (one PR + checks query for the focused session), be cached/debounced, and degrade gracefully — not fan out to every tracked PR binding in parallel and consume a day's practical quota in minutes, and not double-spend past the limit.
Impact
Any user running multiple PR-attached sessions loses their entire GitHub GraphQL quota to the app shell, which then breaks gh-based workflows inside their sessions (merge queues, CI watchers) in ways that look like GitHub flakiness. Workaround we've adopted: run GitHub-heavy sessions from a plain terminal, where the desktop shell's poller is not present.
3 Comments
Refined measurement — the cost is per session-switch, and it's a constant. A controlled re-run against a proven-zero baseline on a fresh quota window corrects two attributions in the original report:
Measurement notes for anyone reproducing: once
remaininghits 0 the client backs off andusedfreezes, so a saturated window "measures" zero burn — only measure on a fresh window.rate_limititself is exempt and safe to poll.This localizes the defect precisely: whatever GraphQL batch
GhRestClientissues on session focus/switch costs ~569 points per invocation and is uncached — two switches to the same session pay twice.Independent reproduction later the same day, with a controlled zero baseline and an app-quit control. Numbers land in the same band as the report, and the method turned up a measurement artifact that will make this look unreproducible to anyone who tries it naively.
Per-switch cost, against a proven-zero baseline
All on one fresh quota window, sampling
/rate_limitevery 10s (that endpoint is exempt, so it does not perturb the measurement):1706 / 3 = 569 points per session switch. Earlier the same evening, unattended bursts measured 570, 570, 575 and 433 at ~30s intervals while the machine was idle — so the batch size is a constant rather than a function of the action. Consistent with the ~640/focus figure above; the difference is presumably which of focus/warm-up/binding refresh each action triggers.
Budget implication, which is the part that bites: 5000 / 569 ≈ 8–9 session switches per hour before the pool is gone.
It is per-action, not a background poller
Worth stating explicitly because it changes where to look: with the app running and untouched, consumption is flat zero — 5 minutes idle before the switches, and flat again the instant switching stopped. A full quit costs nothing to verify: 4m41s with the process gone, zero points, then 12 points total on restart. So nothing is polling on a timer; the cost is attached to the action.
The artifact: once the pool is exhausted, the counter stops telling the truth
This wasted my first attempt entirely, and anyone reproducing this will hit it.
After the limit is reached,
usedkeeps climbing well past it — I saw 6,986 then 10,274 against a 5,000 limit — and then freezes, because the client backs off after repeated 403s. A frozen counter is indistinguishable from zero consumption.My first experiment quit the app for 33 minutes against an already-saturated pool and dutifully reported 0 pts/min in every phase — app running, app gone, app restarted. Taken at face value that exonerates the app completely. It was measuring nothing.
Two consequences for anyone testing this:
remainingback at 5000). At ~569/switch it re-saturates in under 5 minutes, so the usable measurement budget is small.Reproduction harness
A detached sampler survives the app being quit, and logs app liveness alongside the counter:
Two macOS gotchas that silently corrupt this:
setsiddoes not exist (usenohup … &), andpgrep -freturns zero matches on the/Applications/Claude.app/Contents/MacOS/Claudepath, so a liveness column built on it logs GONE while the app is plainly running.ps aux | grepworks.Environment
Claude Code CLI 2.1.234, macOS Darwin 25.5.0, 182 session transcripts across 16 project dirs, several PR-bound. REST core usage over the same period: 11 points, i.e. the burn is entirely GraphQL and entirely not
gh.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.