Claude Code stuck at 0 tokens? Here's what's actually happening
11 min read

On this page
A spinner stuck at 0 tokens for more than a few seconds means one of three things, and they need three different responses: the request never actually reached Anthropic's API (a network problem), Claude Code is quietly waiting on something else entirely - most often a synchronous hook - before it can move (a display problem, not a network one), or you're mid-retry after a transient server hiccup and it will resolve on its own within the retry window. Only one of the three needs you to touch anything.
Waiting for API response... will retry in Ns message first - that's a normal retry, not a hang, and it clears itself. No retry message and the spinner has sat at 0 tokens for a while right after a background task or subagent finished? That's very likely a synchronous Stop or PostToolUse hook blocking the session, not the network - Claude Code's own hooks reference sets the default timeout for a command hook at 600 seconds, so a slow hook can hold the UI for ten minutes before anything cancels it. No hook involved and no retry message either? You're looking at the network-level bug tracked in #6008, most often caused by a proxy, Node 22+, or IPv6 routing.Is your session actually dead, or just quiet?
Three things produce the exact same "0 tokens" spinner, and only two of them are bugs:
- A retry in progress. If a request gets no data back for 20 seconds while still pending, Claude Code shows
Waiting for API response... will retry in Nsper its own error reference. This is working as designed - a transient server error or a slow upstream response, retried automatically up toCLAUDE_CODE_MAX_RETRIEStimes. - A hook holding the process. Something you configured - most often a
StoporPostToolUsehook - is running synchronously and hasn't returned yet. The model isn't generating anything, so the counter genuinely reads 0, but nothing is actually stuck; your own script just hasn't finished. - A real network-level hang. The request never left your machine, or never got a response, and nothing is coming - not a hook, not the API, nothing. This is the one with an open GitHub issue and no first-party fix.
Network hang
Request never left, or never got a response. Indefinite, no trigger event, #6008.
Hook blocking
A synchronous Stop/PostToolUse hook hasn't returned. Starts right after a task finishes.
Normal retry
"Waiting for API response... will retry in Ns" is visible. Resolves on its own.
The next three sections cover each one, in the order they're worth ruling out.
The network-level hang: proxy, Node version, and IPv6
#6008, filed August 2025 and still open a year later with 23 comments, is the exact match for this symptom: Claude Code sits at 0 tokens indefinitely, no error, claude --verbose shows nothing useful, and the only way out is killing the session. Anthropic's own auto-triage bot flagged it as a possible duplicate of three even older issues when it was filed - this specific failure mode has been reported, independently, for well over a year without a first-party fix landing.
What the thread has instead is two community-diagnosed causes, both confirmed by people who fixed their own installs:
- Node 22+ and a proxy. Node's built-in
fetchruns onundicistarting in Node 22, andundicihas a documented issue with proxy support (nodejs/undici#4086). One reporter downgraded to Node 20 and reinstalled Claude Code; the hang stopped. - IPv6 routing that doesn't actually work. A different reporter had DNS resolving
api.anthropic.comto an IPv6 address on a network where IPv6 wasn't actually routable - the connection just went nowhere. The tell:curl -6 -v --connect-timeout 5 https://api.anthropic.comreturns "Network is unreachable" while the same command with-4succeeds. The fix is forcing IPv4 preference in/etc/gai.conf(precedence ::ffff:0:0/96 100) or disabling IPv6 outright.
Neither cause is Claude Code's own code doing something wrong - both are the request failing to leave your machine's network stack cleanly, which is exactly why the CLI itself has nothing useful to say about it. If you're behind a corporate proxy or on Node 22+, that's the first thing to rule out; if you're on an IPv6-heavy network, the curl -6 test above takes ten seconds and tells you immediately.
The UI-only version: when a hook is blocking, not the network
A second, unrelated cluster of reports describes something that looks identical from the outside but has nothing to do with the network. The pattern: a background task or subagent finishes, the spinner text changes (Germinating..., Harmonizing..., Generating...), the token counter reads 0, and it just sits there - sometimes for over an hour - until you press Esc.
Four separate bug reports trace the same symptom, filed within a day of each other in January 2026 and auto-merged into one another by Claude Code's own duplicate-detection bot:
Agent gets stuck in 'working' state with 0 tokens after completing turn
opened 2026-01-23 - closed 2026-01-27, dup of #20417
"Harmonizing" state with 0 tokens and no way to gracefully exit
opened 2026-01-23 - closed 2026-01-27, dup of #20346
UI: 'Germinating...' spinner doesn't clear after background task
opened 2026-01-23 - closed 2026-01-27, dup of #20171
Phantom 'Generating...' state - UI stuck after task completion
opened 2026-01-22 - 28 comments - root cause identified in comments, still unfixed
#20171, the root of that chain, is still open with 28 comments five months later - and one of those comments is the actual root cause, reported by a developer who traced the identical symptom in their own IDE plugin built on the Claude Agent SDK. The claude-mem plugin (v10+) installs a synchronous Stop hook that runs a summarize job after every assistant turn. That hook blocks the CLI process for 3-7 seconds normally, or up to 110 seconds when its worker pool is saturated (thedotmack/claude-mem#1601, per the reporter's own comment on #20171). During that window, the UI shows "Generating..." with 0 tokens because nothing is actually generating - the process is just waiting on your own hook to hand back control.
claude-mem is one specific plugin, but the mechanism generalizes to any hook you've configured on Stop, SubagentStop, or PostToolUse that does real work synchronously: a linter, a test run, a call out to another API, a summarization step. If it doesn't return quickly, the session doesn't either.
Diagnostic: check whether you have claude-mem installed (~/.claude/plugins/cache/thedotmack/claude-mem/), and more generally, scan your own hooks config for anything synchronous on those three events:
for f in ~/.claude/settings.json .claude/settings.json .claude/settings.local.json; do
[ -f "$f" ] || continue
jq -r --arg src "$f" '
.hooks // {} | to_entries[] |
select(.key == "Stop" or .key == "SubagentStop" or .key == "PostToolUse") as $event |
$event.value[]?.hooks[]? |
select(.type == "command" and (.async // false) != true) |
"\($src): \($event.key) -> \(.command) (timeout=\(.timeout // 600)s, async=false)"
' "$f" 2>/dev/null
done
Run against this project's own settings, it returns nothing - no synchronous Stop, SubagentStop, or PostToolUse hooks are configured here, which is the expected result on a clean install. On a machine where it prints a line, that command is your suspect: time how long it actually takes to run standalone, and compare that to how long your sessions sit at 0 tokens.
Why nothing steps in and cancels it for you
The reason a slow hook can hold a session for minutes instead of seconds, per Claude Code's current hooks reference, is the default timeout:
marker at 110s: claude-mem's documented worst-case block, well inside the 600s ceiling
lowers the command default from 600
lowers it further, to 10
A command hook - the type both Stop and PostToolUse normally use - gets 600 seconds, ten minutes, before Claude Code cancels it and discards its output. claude-mem's worst-case 110-second block is comfortably inside that window, so the timeout never fires; the session is just genuinely waiting, the whole time, for a hook that hasn't hit the point where Claude Code would give up on it. UserPromptSubmit hooks get 30 seconds and MessageDisplay hooks get 10, which is why those two events rarely produce a report like this - they hit their much shorter ceiling long before a user notices ten minutes have passed.
The fix that actually works, once you've identified the hook: add "async": true to that hook's definition. Claude Code documents that the timeout "isn't enforced" on a command hook run with async: true - the session stops waiting on it and moves on, and the hook keeps running in the background instead of holding the turn hostage. Lowering timeout to something short is the other option, but a canceled hook still "renders no decision" per the docs, so if the hook's output matters to your workflow, async: true is the fix and a short timeout is just a smaller version of the same interruption.
Checking your real token usage while the counter reads 0
None of the three causes above mean your usage stopped being tracked - the counter not moving is a display symptom, not an accounting one. /usage shows your session total on demand, and /context breaks down what's currently filling the context window; both work regardless of whether the live spinner is updating. Why your Claude Code token count disappeared covers a related but different bug - the counter going missing entirely rather than sitting at 0 - and walks through a third option, reading the session's own JSONL transcript file directly, if you want a number that doesn't depend on the CLI cooperating at all.
Getting unstuck without losing the session
What you do next depends on which of the three causes matches:
| Cause | Typical duration | Confirm with | Fix |
| --- | --- | --- | --- |
| Retry in progress | Seconds, resolves on its own | The visible will retry in Ns message | Nothing - wait, or raise API_TIMEOUT_MS if it happens on legitimately large requests |
| Hook blocking | Matches the hook's own runtime, up to its timeout (600s default) | The hooks scan above returns a match; spinner started right after a background task finished | Add "async": true to that hook, or shorten its timeout |
| Network hang | Indefinite - won't resolve without intervention | claude --debug shows no outbound activity; curl -6 fails where -4 succeeds | Downgrade Node to 20, disable IPv6 preference, or rule out a proxy var |
For the network hang specifically, Esc or Ctrl+C is the only documented way out - none of the open reports describe a way to recover the in-flight request itself. Your conversation history up to that point is preserved; you're only losing whatever the stuck turn would have produced. If you're mid-way through a long agentic task, /resume after restarting picks the session back up from the last completed turn rather than losing the whole thing.
When it's not actually stuck
A single tool call that reads or writes a large file, runs a long test suite, or waits on a slow MCP server can legitimately show 0 tokens for a while - no tokens are being generated because Claude is waiting on that one call to return, and that's expected, not a bug. The signal that separates a real problem from a long-but-normal wait is duration relative to what the call is actually doing: a Bash call running a 90-second test suite sitting at 0 tokens for 90 seconds is correct behavior. The same 0-token spinner outliving the task that's supposedly running, or persisting well past when a background task already reported done, is the pattern worth investigating with the sections above.
FAQ
- Is "Claude Code stuck at 0 tokens" a known, confirmed bug? Partly. The network-level version (#6008) is open with no confirmed fix. The UI-only version's root issue (#20171) is also still open, but has an identified cause in the comments: a synchronous hook (
claude-mem'sStophook, in the traced case) blocking the session within its 600-second default timeout. - How do I tell a hook block from a real network hang? A hook block starts right after a background task, subagent, or tool call finishes, and its duration roughly matches how long that hook actually takes to run. A network hang has no such trigger - it can happen on a plain "hi" with nothing else running, and
curl -6 -v https://api.anthropic.comwill often fail where-4succeeds if IPv6 routing is the cause. - Does this mean my token usage isn't being tracked? No. The status-bar counter is a display, not the source of truth.
/usageand/contextboth report your real usage on demand regardless of whether the live counter is updating. - Why doesn't Claude Code just cancel a hook that's taking too long? It does, eventually - the default timeout for a
commandhook is 600 seconds. A hook that blocks for under ten minutes, likeclaude-mem's documented worst case of 110 seconds, never hits that ceiling, so the session waits out the whole thing rather than getting cut off early. - What actually fixes a hook-caused hang? Add
"async": trueto the offending hook's entry inhooks.jsonso Claude Code stops waiting on it. Removing or disabling the hook ("Stop": []for theclaude-memcase specifically) works too, at the cost of whatever that hook was doing for you.
If hooks turn out to be the cause, what a hook actually costs you in tokens is worth reading next - the same synchronous hooks that can block a session are also the ones most likely to be writing unfiltered output back into your context every single call. The hook token cost calculator turns a pasted hooks config into a per-session estimate if you'd rather not do that audit by hand.
See your own numbers
These are aggregates from real sessions. Your setup is different - run the free scan and get the breakdown for your own Claude Code history. It runs locally; nothing about your code or prompts leaves your machine.
npx usagecutRun a free scan →UsageCut by ClockedCode - not affiliated with Anthropic. The figures on this page are measured on real Claude Code sessions and labeled measured or estimated where it matters.