Claude Code knows how much of your usage limits you have left, but it shows you
in a panel you have to go and open. The number belongs where you are already
looking, so it went into the VS Code status bar:
Claude 5h 12% · 7d 39%
Enter fullscreen mode
Exit fullscreen mode
Hover for every limit and when each one resets; the item turns amber at 75% and
red at 90%. The first version worked. The second, released the same evening, had
the usage endpoint answering 429: five requests in fifteen seconds, and every one
of them extended the wait.
Every window runs your extension again
The mistake was thinking of the extension as one program. It isn’t. Each VS Code
window runs its own copy, with its own timers and its own event handlers, and
nothing is shared between them unless you share it.
So the real schedule was never “one request every five minutes”. It was:
- every open window, polling on its own timer,
- plus a fetch whenever any window gained focus,
- plus a fetch on every click of the item, including while the server was
saying wait.
Move between a few windows, click the item because the number looks stale, and
you get what happened here: five requests in fifteen seconds, each one making
the block longer.
One result, shared through a file
VS Code gives every extension a global storage folder,
context.globalStorageUri, and it is the same folder in every window. That makes
it the simplest place to keep a result that all of them can read.
A window no longer asks the server just because it woke up. It reads the file
first, and only fetches if the numbers there are older than the refresh
interval:
// Fresh enough? Then this is just a render — no request.
const age = last ? Date.now() – last.at : Infinity;
if (age (manual ? CLAIM_MS : s.minutes * 60_000)) 0);
Enter fullscreen mode
Exit fullscreen mode
Two windows can still find the same stale file at the same moment, so the one
that fetches writes a claim first, and any window that sees a claim younger than
30 seconds stays out of it:
// Another window is already fetching.
if (cache.fetchingAt && Date.now() – cache.fetchingAt CLAIM_MS) return;
Enter fullscreen mode
Exit fullscreen mode
The timers also carry up to 20 seconds of random jitter, so windows opened
together don’t wake together. Focusing a window now costs nothing: it re-renders
what is already in the file.
A 429 is an instruction
The worst part of the old version was the click. The item looked stuck, so it
got clicked, and each click was another request to a server that had just said
stop.
Now a 429 sets a block, written to the same shared file so every window obeys it.
Its length is whichever is longer: the server’s own retry-after, or 5 minutes
doubling on each repeat up to 30.
function nextBlock(strikeCount, retryAfterMs) 0);
Enter fullscreen mode
Exit fullscreen mode
And a click cannot override it. That felt wrong to write, because a button that
does nothing looks broken. So the tooltip says when the next check is due, which
turns “why won’t it refresh” into an answer.
A failed check is not an outage
The very first version had the opposite problem: it treated one failure as news.
The extension reads the login Claude Code already keeps: the macOS Keychain
entry Claude Code-credentials, or ~/.claude/.credentials.json elsewhere.
Claude Code rewrites that entry whenever it renews its login, and a read that
lands in that moment comes back without a token. The status bar showed a warning
where the numbers should have been, until the next poll five minutes later.
Now a failure keeps the last numbers on screen, falls back to the token already
held, and retries after 20 seconds, then 60, then 120. The warning appears only
after three failures in a row, and the tooltip always names the last one. Nothing
is hidden; it just stops shouting.
What it touches
Anything that reads a login should say plainly what it does with it:
-
The login, read-only. The token is never refreshed, never written back and
never logged, so the extension cannot sign Claude Code out. If the stored token
has expired, the item says so, and using Claude Code once renews it. -
One request every few minutes, to api.anthropic.com, shared by every
window. - Nothing else: no telemetry, no other network calls.
The endpoint deserves the same honesty. It is the one Claude Code’s own usage
panel reads, not a published API, so a Claude Code update could change or remove
it. If that happens, the extension shows a warning instead of numbers.
Three rules for a polling extension
-
Assume one copy per window. Anything on a timer or a focus event is
multiplied by the number of windows open. Share the result, not the
schedule. -
Treat 429 as an instruction. Store the wait where every copy can see it,
don’t let a button override it, and show when the next try is due. -
One failure is noise. Keep the last good value, retry quickly, and warn
only when failures repeat.
ds_usage is free and MIT licensed, on the
VS Code Marketplace,
with the source on GitHub.
Originally published at devshakib.jumyn.com. I write about Flutter, Dart and the parts of shipping that are genuinely awkward — and publish the packages that came out of them at pub.dev/publishers/jumyn.com.
