Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>Claude Code StatusNew to Visual Studio Code? Get it now.
Claude Code Status

Claude Code Status

Haoyang Zhou

|
3 installs
| (0) | Free
Shows Claude Code activity and token usage in the VS Code status bar.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Claude Code Status

A VS Code status bar item showing what Claude Code is doing and how much of your usage window you have spent.

Marketplace VS Code ^1.90 MIT

The status bar item, magnified: a state icon, the share of your subscription limit, and the time until that limit resets.

How much of your subscription limit you have spent, then the time until it actually resets. Hover for a breakdown; click for a full panel. Set claudeStatus.primaryMetric to cost for locally-measured spend instead.

The icon says what Claude is doing. All five are variations on one mark, drawn by scripts/icons.mjs and shipped as media/claude-status.woff:

The five state icons: needs you, working, background, done, and idle.

The working mark above twinkles at the cadence the status bar actually plays: four frames, ping-ponged, 200 ms apart.

claude-ask Needs your input A question, a plan to approve, or a permission dialog. The item is highlighted too — see attentionStyle.
claude-pulse-* Working Generating, or running a tool. The mark twinkles while the turn is live — see animateWorking.
claude-orbit Background work The turn is over but a subagent, backgrounded command, or async agent is still going. Static on purpose: the main session is not the thing that is moving.
claude-done Done The turn just finished and you have not looked yet.
claude-mark Idle Nothing happening.

The working icon animates by swapping frames on a timer rather than with VS Code's ~spin, which is granted to four built-in codicons only and does nothing on a contributed one. The timer runs only while a turn is actually live in a focused window, so an idle or backgrounded editor costs nothing.

The two states that are easiest to miss are the point: Claude blocked on you, and Claude finished. Both are invisible in a status bar that only knows busy and idle, and both are cases where a transcript that has simply gone quiet looks exactly like one that is done.

With several sessions running, the state shown is the one most worth acting on — needs your input outranks working, which outranks background, done, and idle. A ×3 badge next to the icon means three of them are in that state; the tooltip and panel name each session individually, by conversation title.

Those two figures come from different places, and the extension keeps them visibly apart everywhere — the hover is where you can see the seam:

The hover tooltip: plan limits with their reset times, then usage analysis for this machine, then the sessions by conversation title.

  • Plan limits — your real session and weekly utilization and reset times, reported by Claude for your whole account, across every device.
  • Usage analysis — cost and token totals computed from this machine's transcripts. The open window uses your real session boundaries when a fresh reading is available (tagged actual); earlier ones are grouped from local activity and tagged estimated.

How it works

Claude Code's transcripts, per-process session files, and cached usage block feed the extension, which renders the status bar item, its hover, and the details panel.

Claude Code writes a JSONL transcript for every session under ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. Each assistant message carries the exact usage object the API returned, so this extension reads those files directly. No API key is involved anywhere.

The one thing not derivable from transcripts is how much of your subscription rate limit you have spent. For that the extension refreshes from the same endpoint Claude Code's /usage uses, reusing the OAuth token Claude Code already stores. Set claudeStatus.planLimitSource to cache to turn that off; the extension then reads only Claude Code's local cache and makes no network requests at all. See Plan limits.

Two details of that format matter, and both are handled:

  • One line per content block. A single assistant message is written once per content block, and every line repeats the message's full usage. Naively summing lines roughly doubles the totals; records are keyed on message.id instead.
  • Resumed sessions copy their history. The same message.id reappears in the new session's file. Deduplication is global across files, keeping the earliest timestamp so tokens are attributed to when they were actually spent.

Files are re-read incrementally — only bytes appended since the last scan are parsed — so the default 10-second refresh stays cheap on large histories. A full cold scan of ~3,400 messages across 79 sessions takes about 200 ms.

The 5-hour windows are an analysis grouping, not your limit window

Claude's session limit runs on a 5-hour window, so usage is grouped the same way: a window opens at the top of the hour of its first local message and runs five hours; a message past that, or after a five-hour gap, opens a new one.

These boundaries are not your plan's boundaries, and the extension no longer pretends otherwise. Two reasons they drift:

  • The server anchors on the exact instant of your first request. Flooring to the top of the hour moves the start back by up to 59 minutes, and once one boundary is early every later one is too.
  • Grouping only sees this machine. Usage from claude.ai, another device, or outside lookbackDays never appears, so the message that opens a local window often is not the request that opened the real one.

Observed on a live account: the local window read 6:00 PM → 11:00 PM while the real session window ran 1:19:59 PM → 6:19:59 PM — a countdown wrong by nearly five hours.

A timeline comparing the two: the real window running 1:19:59 PM to 6:19:59 PM against the local estimate of 6:00 PM to 11:00 PM, their starts 4h 40m apart.

The open window is the exception. The API returns resets_at but no start or duration field — however the window is exactly five hours (watched one roll over from 16:20:00 to 21:20:00 UTC), so the current window's start is resets_at - 5h. When a fresh reading is available the extension uses that, labels the window actual, and counts only the messages genuinely inside it. Earlier windows keep the estimate and are labelled estimated.

Earlier boundaries cannot be recovered the same way: windows re-anchor after an idle gap and the phase drifts with them (:10 past the hour on one day, :20 on another), so stepping backwards in five-hour steps produces plausible-looking but wrong boundaries.

Estimated windows therefore show no reset time at all. Reset times appear only under Plan limits, where they are real, and the status bar countdown is omitted entirely when no recent reading is available.

Plan limits

Claude Code caches the result of /usage in ~/.claude.json under cachedUsageUtilization: your session (5-hour) and weekly limits, each with a utilization percentage and a reset time, plus any model-scoped weekly limit. The extension reads that block and shows every limit in the tooltip and the details panel.

That cache is only rewritten when Claude Code actually calls the usage endpoint, so on its own it can sit unchanged for days. By default the extension refreshes it — at most once every planRefreshIntervalSeconds (5 minutes), and never while every session is idle, so an editor left open generates no traffic. It reads the OAuth access token to make that call and never writes it, or anything else, back to disk; ~/.claude.json is Claude Code's state and is only ever read.

Readings are handled honestly rather than optimistically:

  • A limit whose reset time has passed is reported as reset since last check, not as a percentage that no longer describes anything.
  • A reading older than planStaleAfterMinutes (60) is still shown, labelled with its age, but stops driving the status bar figure and its colors.
  • If the refresh fails for any reason the extension quietly falls back to the cached reading. The endpoint is undocumented and may change; a broken refresh degrades to older numbers, never to an error. It also rate-limits, which is why refreshes are throttled and skipped while every session is idle.

A real limit's percentage is what the status bar shows by default (claudeStatus.primaryMetric: planLimit); claudeStatus.planLimitFocus chooses which one:

planLimitFocus Shows
max (default) Whichever limit is closest to being reached.
session Your 5-hour session limit.
weekly Your account-wide weekly limit.
perModel The weekly limit for a specific model, e.g. Opus — the highest of them if your plan has several.

The reset countdown belongs to the same limit as the percentage, so the two are always about one window. It stays a single number — every label, reset time and staleness note lives in the tooltip, which tags the limit the status bar is quoting — and falls back to cost whenever no fresh reading is available, or when your plan has no limit of the kind you picked. It never quietly shows a different limit than the one you asked for.

The amber and red highlight follows the same choice, so the color always agrees with the figure it is coloring. Worth knowing which way that cuts: with planLimitFocus: session, a weekly limit at 95% no longer lights up the item, even though it is the one that will actually stop you. The default max watches every limit on your account and is the safer setting; the tooltip and details panel list all of them either way. Your blockCostLimit budget and the "waiting on you" highlight are unaffected by this setting.

The highlight

The background color has three possible causes, and nothing in the item can say which one fired, so each has its own switch:

Cause Switch
Your blockCostLimit / blockTokenLimit budget claudeStatus.highlightBudget
Your subscription rate limits, per planLimitFocus claudeStatus.highlightPlanLimits
A session waiting on you claudeStatus.attentionStyle (color colors, icon and label do not)

Both highlight switches are color-only: turning one off leaves its figures, tooltip and panel exactly as they were. That is the difference from showPlanLimits: false, which removes the plan feature altogether. Icons stay reserved for what each session is doing — usage never changes the icon.

Waiting-on-you always enters as a warning, so it can never mask a real budget or plan-limit error underneath it.

About the cost figure

Costs are equivalent pay-as-you-go API prices for the tokens recorded, computed from the published per-model rates (including cache reads at 0.1x input, 5-minute cache writes at 1.25x, 1-hour cache writes at 2x, and fast-mode premium rates). On a subscription plan no one is billing you these amounts — treat the number as a consistent measure of consumption. Tokens from models the extension does not recognise are counted in token totals but contribute nothing to cost.

No API publishes prices, so the rate table is compiled in. To price a model released after this version — or to correct a rate that has changed — set claudeStatus.modelRates. Keys are matched as prefixes against the model id, so one entry covers a model's dated snapshots:

"claudeStatus.modelRates": {
  // USD per million tokens.
  "claude-opus-6": { "input": 5, "output": 25 },
  // Optional: fast mode, when it costs more than the standard rates.
  "claude-opus-7": { "input": 5, "output": 25, "fast": { "input": 10, "output": 50 } }
}

Your entries are checked before the built-in table and replace whatever they match, so a broad "claude-opus" reprices every Opus model; among your own entries the longest prefix wins. Cache reads and writes follow from the input rate at the multipliers above.

Settings

Setting Default What it does
claudeStatus.enabled true Show the status bar item.
claudeStatus.primaryMetric planLimit planLimit, cost, tokens, or percent.
claudeStatus.planLimitFocus max Which subscription limit planLimit reports, and which limit the countdown belongs to: max, session, weekly, or perModel.
claudeStatus.modelRates {} Rates for models the built-in table does not know, or corrections to ones it does. See About the cost figure.
claudeStatus.blockCostLimit 0 Budget per 5-hour block, in USD. Enables percent and threshold colors.
claudeStatus.blockTokenLimit 0 Token budget per block. The tighter of the two limits wins.
claudeStatus.warningThresholdPercent 75 Turn the item amber above this share of the block budget, or of a plan limit.
claudeStatus.errorThresholdPercent 90 Turn the item red above this share of either.
claudeStatus.highlightBudget true Let the block budget color the item's background. Off changes the color only.
claudeStatus.highlightPlanLimits true Let your plan limits color the item's background. Off changes the color only.
claudeStatus.scope all all counts every session on the machine; workspace counts only sessions inside this workspace.
claudeStatus.refreshIntervalSeconds 10 How often to rescan.
claudeStatus.lookbackDays 30 Ignore transcripts older than this.
claudeStatus.claudeHome "" Override the Claude directory. Empty auto-detects $CLAUDE_CONFIG_DIR, then ~/.claude.
claudeStatus.showResetCountdown true Show time until the limit beside it resets. Needs a recent plan reading.
claudeStatus.showActivity true Show what each session is doing. Off pins the icon to the resting mark.
claudeStatus.animateWorking true Twinkle the icon while Claude is working. Pauses when the window loses focus, and never runs when workbench.reduceMotion is on.
claudeStatus.attentionStyle color How loudly to flag that Claude is waiting on you: icon, color, or label.
claudeStatus.resultReadyMinutes 5 How long a finished turn reports a ready result. 0 switches the state off.
claudeStatus.activeSessionTimeoutSeconds 120 How recently a transcript must have changed to count as active. A session Claude Code reports as busy or waiting stays listed regardless.
claudeStatus.alignment / claudeStatus.priority right / 100 Placement in the status bar.

Neither limit is set by default, because Anthropic does not publish subscription limits in token or dollar terms. Set blockCostLimit to whatever a heavy block looks like for you (watch the panel for a few days) to get a meaningful percentage and threshold colors.

Commands

  • Claude Status: Show Usage Details — panel with the current block, per-model breakdown, recent blocks, active sessions, and any background work running. Sessions are listed by conversation, since directory and branch do not identify one: parallel conversations in the same repo are common.
  • Claude Status: Refresh Now
  • Claude Status: Open Session Transcript — opens this workspace's transcript.

Logs

Everything on the plan-limit path fails quietly: the status bar falls back to the cost figure rather than showing an error, because a status bar item is the wrong place to report that a refresh did not happen. That leaves a window which has quietly stopped refreshing looking exactly like one whose numbers have not moved, so there is a Claude Code Status output channel that says which it is.

Nothing appears at the default level. Raise it with Developer: Set Log Level… → Claude Code Status:

  • Debug — the events: a request made, a reading adopted from another window, a rate limit and the wait it imposed.
  • Trace — adds a line per tick explaining why no request was made, which is what to turn on when the answer is "nothing is happening".

One line is logged at the default level, when the window starts: the version, the refresh cadences, the directory being read, and whether this host gave the extension storage the other windows can see. It is the first thing worth quoting in a bug report.

No prompt text, transcript content, account id, or token is ever written to it — only counts, decisions and durations.

Development

pnpm install
pnpm test          # 312 unit tests, no VS Code required
pnpm run preview   # print the status bar line against your real ~/.claude
pnpm run smoke     # load the built bundle against a stubbed vscode API
pnpm run watch     # then press F5 in VS Code to launch the extension host

pnpm run preview is the quickest way to check a parsing change against real data; it accepts --home <dir>, --scope-to <dir>, and --limit <usd>.

To build a .vsix:

pnpm run build && npx --yes @vscode/vsce package --no-dependencies

Releases are published by .github/workflows/publish.yml: bump version in package.json, commit, and publish a GitHub release tagged v<version>. One .vsix is built, checked, and sent to the Marketplace. Running the workflow by hand rehearses all of that without publishing.

Layout

File Role
src/scanner.ts Incremental JSONL reader and deduplication.
src/aggregate.ts Pure functions: 5-hour blocks, time windows, burn rate.
src/pricing.ts Per-model rates and cost computation.
src/limits.ts Plan rate-limit parsing, staleness, severity, plan naming.
src/planSource.ts Reads Claude Code's cached /usage block from disk.
src/usageClient.ts Optional live refresh of plan limits, its throttle and backoff.
src/planStore.ts The refresh record every open window shares.
src/planRefresh.ts Assembles a plan reading and decides when to fetch one.
src/log.ts The output channel's seam, and the lines describing a window.
src/view.ts Status bar text, tooltip, severity.
src/panel.ts Details webview.
src/extension.ts Activation, settings, refresh loop.

aggregate, pricing, format, limits, planSource, usageClient, planStore, planRefresh, log, and view have no vscode import, which is why they are directly unit-testable.

Every VS Code window runs its own copy of the extension. Reading transcripts that many times over is cheap and harmless, but the one network call is neither, so the windows coordinate through a small record in the extension's global storage: whichever window is due makes the request, and the rest read its answer instead of making their own. If the endpoint asks for a pause, every window takes it.

Limitations

  • Usage is derived from local transcripts, so it reflects sessions on this machine only — not claude.ai, other devices, or teammates.
  • Rate-limit utilization does not come from transcripts — they carry no rate-limit fields at all. It comes from Claude Code's /usage cache, refreshed over the network unless you turn that off, and is therefore a point-in-time reading rather than something recomputed from local data. With planLimitSource: cache the figures are only as fresh as your last /usage.
  • The blockCostLimit / blockTokenLimit percentages remain measured against a budget you configure, which is unrelated to your real plan limits.
  • Session state comes from two places. Claude Code records what each of its running processes is doing in ~/.claude/sessions/<pid>.json, which is the only way to see that it is blocked on a permission dialog — that leaves no trace in the transcript. Everything else is reconstructed from the transcript itself. The two are reconciled on newest-evidence-wins, because the status file describes only the main thread and can lag it by minutes.
  • Both sources can be wrong for a while. A session killed mid-turn writes no closing record, so it reads as working for ten minutes before timing out; a backgrounded command with no completion notification is written off after six hours. "Needs your input" deliberately never times out, but stops reaching the status bar after eight quiet hours.
  • Model rates are compiled in. A model released after this version ships shows tokens but no cost until the table is updated, or until you price it yourself with claudeStatus.modelRates.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft