What
useVersionCheck is mounted in two places at once:
src/components/sidebar/view/Sidebar.tsx:47
src/components/settings/view/tabs/AboutTab.tsx:32
Each instance keeps its own state and its own timer — src/hooks/useVersionCheck.ts:94 (on main):
const interval = setInterval(checkVersion, 5 * 60 * 1000); // Check every 5 minutes
So one open tab issues two requests to https://api.github.com/repos/siteboon/claudecodeui/releases/latest every five minutes instead of one, and two /health fetches on mount.
Why it matters
The unauthenticated GitHub API allows 60 requests/hour per IP. A team behind one NAT or corporate egress IP shares that budget, and the star badge spends from it too (src/hooks/useGitHubStars.ts:41 calls https://api.github.com/repos/${owner}/${repo}).
When the budget is exhausted GitHub returns 403. That lands in the bare catch at src/hooks/useVersionCheck.ts:85:
console.error('Version check failed:', error);
updateAvailable is left at false, so the outcome of rate limiting is indistinguishable from "you are up to date" — the update banner silently disappears for everyone on that IP, with only a console line to show why.
Suggested direction
Hoist the check to a single source (a context provider, or a store slice) so there is one poller per client regardless of how many components read the result. Handling the 403 explicitly — surfacing "could not check" rather than "no update" — would be worth doing at the same time, though it is a separable concern.
Found while implementing #1187, which gates this poll behind an operator opt-out and therefore makes the duplicated /health fetch load-bearing in both instances.
What
useVersionCheckis mounted in two places at once:src/components/sidebar/view/Sidebar.tsx:47src/components/settings/view/tabs/AboutTab.tsx:32Each instance keeps its own state and its own timer —
src/hooks/useVersionCheck.ts:94(onmain):So one open tab issues two requests to
https://api.github.com/repos/siteboon/claudecodeui/releases/latestevery five minutes instead of one, and two/healthfetches on mount.Why it matters
The unauthenticated GitHub API allows 60 requests/hour per IP. A team behind one NAT or corporate egress IP shares that budget, and the star badge spends from it too (
src/hooks/useGitHubStars.ts:41callshttps://api.github.com/repos/${owner}/${repo}).When the budget is exhausted GitHub returns 403. That lands in the bare catch at
src/hooks/useVersionCheck.ts:85:updateAvailableis left atfalse, so the outcome of rate limiting is indistinguishable from "you are up to date" — the update banner silently disappears for everyone on that IP, with only a console line to show why.Suggested direction
Hoist the check to a single source (a context provider, or a store slice) so there is one poller per client regardless of how many components read the result. Handling the 403 explicitly — surfacing "could not check" rather than "no update" — would be worth doing at the same time, though it is a separable concern.
Found while implementing #1187, which gates this poll behind an operator opt-out and therefore makes the duplicated
/healthfetch load-bearing in both instances.