Why your Claude Code token count is wrong (and how to get the real number)
Short answer: Claude Code writes each AI response to its log several times, and every copy carries the full token count. If you add up every line, you count most responses two or more times.
On our own test machine, a plain sum of 30 days of logs said 100M tokens. The real figure was 50.5M. To get the right number, count each response once. Our free counter does that in your browser, or use the short script below.
Where Claude Code keeps its usage logs
Claude Code keeps a local log of every session. They live in a hidden folder in your home directory:
| System | Folder |
|---|---|
| Mac and Linux | ~/.claude/projects |
| Windows | %USERPROFILE%\.claude\projects |
Inside there is one folder per project, holding .jsonl files (mostly one per session). Each line is
one JSON event. Lines for AI responses carry a usage block with four token counts:
input_tokens: new input sent to the modeloutput_tokens: what the model wrote backcache_creation_input_tokens: input written to the prompt cachecache_read_input_tokens: input read back from the cache (much cheaper than new input)
Why adding up the logs gives the wrong answer
It looks simple: add the usage numbers on every line. But three things in how the logs are written
make that total too high.
- One response, several lines. A single response is written once per content block: thinking, text, and each tool call. Every one of those lines repeats the usage of the whole response. A response with a thinking block, some text and two tool calls can appear four times.
- Resumed sessions copy their history. When you resume a session, earlier lines can be copied into a new log file, so the same response shows up in more than one file.
- Early lines are incomplete. While a response streams, the first lines can carry a partial output count. Only the last one is final, so even picking "any one line" can under-count output.
None of this means you were billed twice. Each response is one request to the API. The over-count only appears when you total the log lines yourself, for example to estimate spending or build a dashboard.
How to count correctly
Every response line carries a message.id and a requestId. Together they identify one
response. So:
- Keep one record per (
message.id,requestId) pair, across all files. - For its output tokens, use the largest value seen for that pair (the final streaming count).
- Skip lines that are not real responses:
"model": "<synthetic>"and API error messages.
Skip the script: drop your projects folder on our free counter and see your real number in a
few seconds. It runs entirely in your browser. Nothing is uploaded.
A short script to do it yourself
This Python 3 script follows the rules above. It only reads your logs and prints three numbers. (Download it.)
import json, pathlib, sys
root = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else pathlib.Path.home() / ".claude" / "projects"
best, plain = {}, 0
for f in sorted(root.rglob("*.jsonl")):
n = 0
for line in f.open(encoding="utf-8", errors="replace"):
try:
o = json.loads(line)
except ValueError:
continue
if not isinstance(o, dict):
continue
n += 1
m = o.get("message")
if o.get("type") != "assistant" or not isinstance(m, dict) or not m.get("usage"):
continue
if m.get("model") == "<synthetic>" or o.get("isApiErrorMessage"):
continue
u = m["usage"]
t = [u.get("input_tokens") or 0, u.get("output_tokens") or 0,
u.get("cache_creation_input_tokens") or 0, u.get("cache_read_input_tokens") or 0]
plain += sum(t)
if m.get("id") or o.get("requestId"):
key = (m.get("id") or "", o.get("requestId") or "")
else:
key = (str(f), n)
if key not in best:
best[key] = t
else:
best[key][1] = max(best[key][1], t[1]) # keep the final (largest) output count
real = sum(sum(t) for t in best.values())
print(f"{len(best):,} responses in {root}")
print(f"Plain sum of the logs: {plain:,} tokens")
print(f"Real usage: {real:,} tokens" + (f" (the plain sum is {plain / real:.2f}x too high)" if real else ""))
Run it with python3 count_claude_code_tokens.py (on Windows: py count_claude_code_tokens.py).
We checked it against our app's engine on our own logs and on sample data: the totals match exactly.
Turning tokens into dollars
Token types are priced differently: output costs several times more than input, cache writes cost a little more than input, and cache reads cost much less. Multiply each type by your model's published per-million price, using the de-duplicated counts. On a subscription plan this shows what the same usage would cost at API rates, not what you paid.
Want this every day, across every AI company?
NeverPayTwice is a small app we make. It keeps this honest count up to date for Claude Code, and for any AI API you route through its live meter (Anthropic, OpenAI, Google Gemini and compatible APIs). It also flags every time you paid again for an answer you had already bought, and keeps a receipt for each purchase. It runs on your own computer (Mac, Windows or Linux), and nothing is uploaded. $9, one time.
See it with sample data first, no sign-up.