Claude Code hung. Which status signal is actually telling you what happened?
Three signals are usually on screen when Claude Code freezes: the status line at the bottom of the terminal, the /status command, and the spinner. All three read local state. None of them can tell you whether Anthropic has rate-limited you on the server. The signal that can answer that lives at claude.ai/settings/usage, and you can read it directly.
The Claude Code status line is fed JSON over stdin with context_window.used_percentage, a local context-window count. It does not show server-side quota. To distinguish a rate-limit hang from a client-side stall, read claude.ai/settings/usage (or its source endpoint /api/organizations/{org}/usage). If five_hour.utilization is at or near 1.0, your hang is a rate-limit and SIGKILL will not help. If both rolling windows are clean and Claude Code has shown zero token consumption for several minutes, the process is wedged client-side and SIGKILL is safe.
Signal 1: the status line
The bar at the bottom of your Claude Code terminal is fed by a script you write. On every update, Claude Code pipes a JSON object to that script's stdin. The fields the docs guarantee are the model name, the workspace directory, cumulative session cost, and the context window's used_percentage.
Every number in this payload comes from inside the local CLI process. There is no field for five-hour quota. There is no field for weekly quota. There is no field for any server-side rate-limit state. The status line is, by design, a window into the client. That makes it useful for context bloat (above ~85 percent and your next prompt will be slow regardless of quota) and useless for quota itself.
Signal 2: the /status command
Running /status inside Claude Code surfaces the current model, the session's context, and any error from the most recent API call. If your last completed call returned a 429 or an anthropic-ratelimit-* header, you'll see it.
The catch is that /status does not poll. If you've been hung for five minutes with no requests in flight, it shows whatever state existed at your last completed call, which is by definition older than the hang itself. And on Pro and Max plans, the rate limiter that fires for Claude Code traffic is the rolling 5-hour and 7-day window on the consumer side, not the API rate-limit headers. So /status can confirm a past 429, but it cannot tell you whether your next prompt will hit the wall.
Signal 3: the spinner (don't trust it)
The animated spinner is driven by the terminal renderer, not by API progress. There are several open issues on anthropics/claude-code documenting hangs where the spinner keeps animating while the underlying request has stalled or never started:
- Issue #25286: terminal renderer reaches 100 percent write ratio, input is buffered and never read.
- Issue #25979: streaming connection stalls mid-response with no read timeout; process stays alive, no progress.
- Issue #25629: stream-json mode hangs indefinitely after the result event due to stdout buffering.
- Issue #44921: zero token consumption for 25-30 minutes; the spinner spins, the API never sees a request.
The spinner means the process is alive. It does not mean a request is moving. Treat it as a liveness check, not as progress.
The one signal that answers the question
GET /api/organizations/{org_uuid}/usage on claude.ai with your logged-in claude.ai cookies and Referer: https://claude.ai/settings/usage. The response is JSON with seven Window-shaped objects. Each Window has exactly two fields: a utilization fraction and a resets_at timestamp. The same endpoint claude.ai/settings/usage renders. The same field the rate limiter checks.
Side by side: what each signal can answer
| Feature | Status line / /status / spinner | Server endpoint |
|---|---|---|
| Reads server-side quota | No. Pulls JSON from local stdin only. | Yes. Reads /api/organizations/{org}/usage directly. |
| Updates while no request is in flight | No. The status line only fires on Claude Code lifecycle events. | Yes. Polls every 60 seconds independently of Claude Code. |
| Shows whether you'll 429 on the next prompt | No. Nothing in the stdin payload reflects rate-limit state. | Yes. five_hour.utilization is the same field the rate-limiter checks. |
| Shows resets_at countdown | No. | Yes. Each Window carries its own resets_at timestamp. |
| Shows extra-usage dollars spent for metered billing | No. | Yes. Companion /overage_spend_limit endpoint, used_credits in cents. |
| Includes traffic from other devices on the same account | No. Local stdin payload covers this CLI process only. | Yes. Server aggregates across devices before computing utilization. |
| Includes claude.ai browser-chat traffic | No. That traffic never enters Claude Code's stdin. | Yes. The server applies it to the same buckets. |
| Survives a hung Claude Code process | No. If Claude Code is wedged, it stops piping JSON to the script. | Yes. The browser extension runs in Chrome, independent of the CLI. |
Walking the decision: hang vs rate-limit, in five steps
When Claude Code locks up mid-refactor, you have under a minute before context-switching to a different terminal feels worse than the hang itself. Run this in order. It's designed to rule things out cheaply.
Glance at the status line first.
If context_window.used_percentage is above ~85, your context is bloated and Claude is spending real time recomputing across the whole transcript on every prompt. That looks like a hang but it isn't, it's just slow. Press Esc, run /compact, and try again. If the percentage is below 70, context bloat is not your problem and you can rule it out.
Run /status next.
If the most recent API call returned a 429 or a rate-limit error, /status will show it. If /status reports an active error, you have a server-side rate-limit and your job is to wait for resets_at, not to debug the CLI. If /status looks clean, the hang predates any error response and you need a live signal, which /status cannot give you.
Read the server endpoint directly.
Open claude.ai/settings/usage in a browser tab, or curl /api/organizations/{org}/usage. If five_hour.utilization is at or above 0.95, you are about to be (or have just been) rate-limited and the hang is the queue forming on the server side. If both five_hour and seven_day utilization are below 0.80, the server has plenty of headroom and your hang is not a quota problem.
Branch on what you found.
Rate-limit branch: don't SIGKILL, the next prompt will hit the same wall. Read resets_at; switch to extra-usage if your plan has it enabled; otherwise wait. Client-side branch: press Esc, then if the process doesn't recover in 30 seconds, pkill -f claude in another shell. The wedged-renderer hang on issue #25286 specifically requires SIGKILL because Esc is buffered behind the renderer and never read.
Make this signal continuous, not a curl.
Polling claude.ai/settings/usage manually every time Claude Code feels stuck is a context-switch tax you'll pay dozens of times a week. The point of ClaudeMeter is to make the server signal a glance instead of a workflow: the macOS menu bar shows the worst bucket's utilization plus a countdown to the soonest resets_at, refreshed every 60 seconds. Same number, no alt-tab.
Reproduce it yourself with curl
Two requests answer the question. The first finds your org UUID (which is needed because the usage endpoint is org-scoped). The second reads quota for that org. Both reuse your existing claude.ai cookie; nothing is pasted.
The Referer header is load-bearing. Drop it and the endpoint returns 403. The server checks Referer as part of its CSRF story; both ClaudeMeter routes (extension and binary) set it explicitly.
Why local tools structurally cannot answer this
ccusage and Claude-Code-Usage-Monitor both walk ~/.claude/projects/*.jsonl and sum tokens. That sum is real and useful as a measurement of token flow on this device. It is not the function the rate-limiter computes on the server. The server applies weights local logs cannot see:
- A peak-hour multiplier on weekday US Pacific midday hours.
- A per-attachment cost that fires the moment you upload a PDF or image.
- A per-tool-call cost on code execution and web browsing.
- A per-model weight where Opus burns faster than Sonnet for the same prompt.
- Any prompt sent on
claude.aiin the browser, which never lands in the local JSONL files. - Any traffic from other devices on the same account.
Stack a few of those up and the local count and the server count can diverge by an order of magnitude. There are reproducible reports of ccusage saying 5 percent while the next prompt 429s. The local number isn't wrong; it's answering a different question.
Where each signal lives in the stack
Four layers, three of them local, only one of them authoritative for quota
Local: Claude Code CLI
Reads context_window.used_percentage. Writes status line via stdin to your script. Knows nothing about server quota.
Boundary: api.anthropic.com
Returns anthropic-ratelimit-* headers on each response. Visible in /status only after a request completes; not a poll.
Server-truth: claude.ai/api/organizations/{org}/usage
Returns five_hour.utilization and six other buckets. The same endpoint claude.ai/settings/usage renders. Polled every 60s by the ClaudeMeter extension.
Display: macOS menu bar
Worst-bucket percentage and the soonest resets_at. Color-coded: green under 80, amber 80-100, red at 100. Independent of the Claude Code process state.
Making the server signal continuous
Curling /api/organizations/{org}/usage every time Claude Code feels stuck is a context-switch tax. The ClaudeMeter extension does this for you on a 60-second cadence (POLL_MINUTES = 1 in extension/background.js) and renders the worst bucket plus the soonest resets_at in the macOS menu bar. Color-coded: green under 80, amber 80 to 100, red at 100. When Claude Code hangs, you glance at the menu bar instead of opening claude.ai/settings/usage in a tab.
One install command (brew install --cask m13v/tap/claude-meter), one extension load, no cookie paste. The extension reads your existing claude.ai session from the browser; the menu-bar app listens on 127.0.0.1:63762 for snapshots. No telemetry, MIT licensed, single HTTPS request per minute to claude.ai.
Your Claude Code keeps stalling at random times of day. Want a 15-minute walkthrough?
Bring a session log. We'll trace which of the four hang patterns matches and whether the server-side wall is what's actually firing.
Frequently asked questions
How do I tell if Claude Code is hung or rate-limited?
The status line at the bottom of the terminal reads context_window.used_percentage from a JSON object piped over stdin. That is local context-window utilization, not server-side quota. To check if you've actually been rate-limited, open claude.ai/settings/usage in a browser tab. If the five-hour or seven-day bar is at or near 100 percent, the hang is a rate-limit and waiting is the only fix until resets_at. If both bars are clean and the process has consumed zero tokens for several minutes, the hang is client-side, almost certainly the streaming-stall pattern from issue #25979 or the wedged-renderer pattern from issue #25286, and SIGKILL is safe.
What does the Claude Code status line actually show?
It is a customizable bar fed by a script you write. Claude Code pipes a JSON object to that script's stdin on each update, with fields for the model display name, the workspace directory, cost so far in the session, and context_window.used_percentage. A common config is jq -r '"[\(.model.display_name)] \(.context_window.used_percentage // 0)% context"'. Every field is computed inside the local CLI process. There is no field for server quota, no field for five-hour utilization, and no field for weekly utilization. The status line is a window into the client; it cannot be a window into the server.
Does /status show server-side rate-limit state?
Partially, and only retroactively. /status surfaces the current model, the session's local context, and any error from the most recent API call. If your last call returned an anthropic-ratelimit-* header or a 429, you'll see it. But /status does not poll. If you've been hung for five minutes with no requests in flight, /status will show whatever state existed at your last successful call, which is older than the hang itself. It is a snapshot of the past, not a live signal of the rate-limit ceiling.
If the spinner is moving, is Claude Code working?
Not reliably. The spinner is driven by the terminal renderer, not by API progress. Issue #25286 documents a pattern where the renderer keeps animating at a 100 percent write ratio while the underlying request has stalled. Issue #44921 documents a pattern where token consumption sits at zero for 25-30 minutes while the spinner keeps spinning. The spinner means the process is alive. It does not mean a request is moving.
What is the one signal that actually answers the question?
GET the path /api/organizations/{your-org-uuid}/usage on the claude.ai host with your logged-in claude.ai cookies and a Referer header pointing to claude.ai/settings/usage. The response is JSON with seven Window objects (five_hour, seven_day, seven_day_sonnet, seven_day_opus, seven_day_oauth_apps, seven_day_omelette, seven_day_cowork) each shaped { utilization: f64, resets_at: Option<DateTime<Utc>> }. The same endpoint claude.ai/settings/usage renders the bar from. If five_hour.utilization is at or near 1.0, you've been rate-limited. ClaudeMeter polls this endpoint once a minute via your existing browser session and renders the worst bucket in the macOS menu bar.
Why can't ccusage tell me whether I've been rate-limited?
Because ccusage walks ~/.claude/projects/*.jsonl and sums local input/output tokens. That sum is correct as a token-flow measurement on this device. It is not the function the server's rate-limiter is computing. The server applies weights local logs cannot see (a peak-hour multiplier on weekday US Pacific midday hours, a per-attachment cost, a per-tool-call cost, a per-model weight where Opus burns faster than Sonnet for the same prompt, plus any traffic from claude.ai browser chats or other devices on the same account). The local sum and the server's utilization can diverge by an order of magnitude. There are reproducible reports of ccusage saying 5 percent while claude.ai 429s the next request.
If I see five_hour.utilization at 0.95 while Claude Code is hung, what should I do?
Three things, in this order. (1) Don't SIGKILL; the next prompt will hit the same wall. (2) Read resets_at to know when the wall lifts; the value is an ISO timestamp like 2026-05-09T19:22:00Z, which is when the oldest chargeable traffic in the bucket ages out. (3) If extra usage is enabled on your plan, switch to it for the rest of the session: Anthropic's metered overage path keeps working when the rolling-window bucket is full. ClaudeMeter renders the dollar amount used on extra usage next to the bar so you can see where you stand at a glance.
If both bars are clean and Claude Code is still hung, what's actually wrong?
It's a client-side stall. The four documented patterns: (1) the stream-json mode hang on issue #25629, where stdout buffering wedges after the result event; (2) the streaming-stall hang on issue #25979, where the SSE connection stops emitting events but the request stays open with no read timeout; (3) the wedged-renderer hang on issue #25286, where the terminal renderer reaches 100 percent write ratio and refuses input; (4) the zero-token-consumption hang on issue #44921, where the process runs but never sends a request. Press Esc once. If the process doesn't recover in 30 seconds, run pkill -f claude in another shell.
What about the anthropic-ratelimit-* headers?
Those are on responses from the Anthropic API surface (api.anthropic.com), not from claude.ai. They give you the most restrictive currently-active API rate limit for your API key (tokens-per-minute, requests-per-minute, input-tokens-remaining). They do not expose the rolling 5-hour or 7-day consumer-plan utilization that gates Claude Code on a Pro or Max subscription. The consumer plan's utilization comes back only on the private claude.ai endpoint. Different surface, different contract, not interchangeable.
Why doesn't Claude Code show server quota in the status line itself?
Because the CLI is given an API key or session credential at startup and talks only to api.anthropic.com or the equivalent Claude Code-specific endpoint. The consumer-plan quota lives on claude.ai under a different cookie scope. To bridge them, something has to call claude.ai/api/organizations/{org}/usage with your browser cookies and feed the result somewhere visible. That's exactly what ClaudeMeter's browser extension does: it runs inside Chrome, calls the endpoint with credentials: 'include', and POSTs the snapshot to the menu-bar app on localhost:63762. The status line could in principle shell out to that bridge, but it doesn't ship that way out of the box.
More on the server-truth gap
Server quota is a fraction with a private denominator
Why local token counters can't see what claude.ai/settings/usage actually enforces, and the two-field Window struct it returns instead.
Local counter vs server quota, side by side
Why ccusage says 5 percent while claude.ai 429s the next request. The weights local logs cannot see.
Burn rate against a rolling window, not a calendar window
How utilization drifts minute to minute even when you stop sending messages, and why a sample from 30 minutes ago is usually wrong.