{"slug": "five-requests-in-fifteen-seconds-from-one-status-bar-item", "title": "Five Requests in Fifteen Seconds From One Status Bar Item", "summary": "A developer built ds_usage, a free MIT-licensed VS Code extension that displays Claude Code usage limits in the status bar, showing 5-hour and 7-day percentages with amber and red warnings at 75% and 90%. After an initial version triggered HTTP 429 rate limits from five requests in fifteen seconds across multiple VS Code windows, the developer reworked it to share cached results and fetch claims through VS Code's global storage folder, add randomized timer jitter, and enforce server-driven backoff that clicks cannot override.", "body_md": "Claude Code knows how much of your usage limits you have left, but it shows you\n\nin a panel you have to go and open. The number belongs where you are already\n\nlooking, so it went into the VS Code status bar:\n\n```\nClaude 5h 12% · 7d 39%\n```\n\nHover for every limit and when each one resets; the item turns amber at 75% and\n\nred at 90%. The first version worked. The second, released the same evening, had\n\nthe usage endpoint answering 429: five requests in fifteen seconds, and every one\n\nof them extended the wait.\n\nThe mistake was thinking of the extension as one program. It isn't. Each VS Code\n\nwindow runs its own copy, with its own timers and its own event handlers, and\n\nnothing is shared between them unless you share it.\n\nSo the real schedule was never \"one request every five minutes\". It was:\n\nMove between a few windows, click the item because the number looks stale, and\n\nyou get what happened here: five requests in fifteen seconds, each one making\n\nthe block longer.\n\nVS Code gives every extension a global storage folder,\n\n`context.globalStorageUri`, and it is the same folder in every window. That makes\n\nit the simplest place to keep a result that all of them can read.\n\nA window no longer asks the server just because it woke up. It reads the file\n\nfirst, and only fetches if the numbers there are older than the refresh\n\ninterval:\n\n``` js\n// Fresh enough? Then this is just a render — no request.\nconst age = last ? Date.now() - last.at : Infinity;\nif (age < (manual ? CLAIM_MS : s.minutes * 60_000)) {\n  render();\n  return;\n}\n```\n\nTwo windows can still find the same stale file at the same moment, so the one\n\nthat fetches writes a claim first, and any window that sees a claim younger than\n\n30 seconds stays out of it:\n\n```\n// Another window is already fetching.\nif (cache.fetchingAt && Date.now() - cache.fetchingAt < CLAIM_MS) return;\n```\n\nThe timers also carry up to 20 seconds of random jitter, so windows opened\n\ntogether don't wake together. Focusing a window now costs nothing: it re-renders\n\nwhat is already in the file.\n\nThe worst part of the old version was the click. The item looked stuck, so it\n\ngot clicked, and each click was another request to a server that had just said\n\nstop.\n\nNow a 429 sets a block, written to the same shared file so every window obeys it.\n\nIts length is whichever is longer: the server's own `retry-after`, or 5 minutes\n\ndoubling on each repeat up to 30.\n\n``` js\nfunction nextBlock(strikeCount, retryAfterMs) {\n  const curve = Math.min(BLOCK_MAX, BLOCK_MIN * 2 ** Math.max(0, strikeCount - 1));\n  return Math.max(curve, retryAfterMs || 0);\n}\n```\n\nAnd a click cannot override it. That felt wrong to write, because a button that\n\ndoes nothing looks broken. So the tooltip says when the next check is due, which\n\nturns \"why won't it refresh\" into an answer.\n\nThe very first version had the opposite problem: it treated one failure as news.\n\nThe extension reads the login Claude Code already keeps: the macOS Keychain\n\nentry `Claude Code-credentials`, or `~/.claude/.credentials.json` elsewhere.\n\nClaude Code rewrites that entry whenever it renews its login, and a read that\n\nlands in that moment comes back without a token. The status bar showed a warning\n\nwhere the numbers should have been, until the next poll five minutes later.\n\nNow a failure keeps the last numbers on screen, falls back to the token already\n\nheld, and retries after 20 seconds, then 60, then 120. The warning appears only\n\nafter three failures in a row, and the tooltip always names the last one. Nothing\n\nis hidden; it just stops shouting.\n\nAnything that reads a login should say plainly what it does with it:\n\n`api.anthropic.com`, shared by every\nwindow.\nThe endpoint deserves the same honesty. It is the one Claude Code's own usage\n\npanel reads, not a published API, so a Claude Code update could change or remove\n\nit. If that happens, the extension shows a warning instead of numbers.\n\nds_usage is free and MIT licensed, on the\n\n[VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=devShakib.ds-usage),\n\nwith the source on [GitHub](https://github.com/devShakib015/ds_usage).\n\n*Originally published at [devshakib.jumyn.com](https://devshakib.jumyn.com/blog/five-requests-in-fifteen-seconds-from-one-status-bar-item). 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](https://pub.dev/publishers/jumyn.com/packages).*", "url": "https://wpnews.pro/news/five-requests-in-fifteen-seconds-from-one-status-bar-item", "canonical_source": "https://dev.to/devshakib/five-requests-in-fifteen-seconds-from-one-status-bar-item-4kgh", "published_at": "2026-09-14 16:40:57+00:00", "updated_at": "2026-09-14 16:55:23.877419+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-products"], "entities": ["Claude Code", "VS Code", "Anthropic", "ds_usage", "VS Code Marketplace"], "alternates": {"html": "https://wpnews.pro/news/five-requests-in-fifteen-seconds-from-one-status-bar-item", "markdown": "https://wpnews.pro/news/five-requests-in-fifteen-seconds-from-one-status-bar-item.md", "text": "https://wpnews.pro/news/five-requests-in-fifteen-seconds-from-one-status-bar-item.txt", "jsonld": "https://wpnews.pro/news/five-requests-in-fifteen-seconds-from-one-status-bar-item.jsonld"}}