Table of contents
- 1. What the official screens show, and what they don't
- 2. The answer is in your local conversation logs
- 3. Add it up naively and you get about double: four pitfalls
- 4. The aggregation script
- 5. Measured: one of 29 sessions used nearly a third
- 6. How to read the numbers, and where they stop
- 7. To keep watching from here on, use OpenTelemetry
- FAQ
Run several Claude Code sessions in parallel and your weekly limit drains faster than you expect. You want to know which session is eating it, but opening /usage won't tell you.
The short answer: as of September 2026, there is no official screen that shows what share of your usage each session took. /usage gives you the numbers for the current session, plus your plan-wide consumption split into percentages by skill, subagent, plugin and MCP server. If you want it by session, you have to aggregate the conversation logs stored on your own machine.
Count those logs naively, though, and you get it wrong. When I measured the last 7 days of logs on my machine, simply adding up the lines gave a total 2.06× the correct value. Worse, even among just the 12 heaviest sessions, the size of that error varied from 1.30× to 3.02×, so the ranking changed. This article walks through what the official screens do show, the right way to count, an aggregation script of about 50 lines, and what I measured.
Which sessions used the last 7 days
29 sessions on my machine, weighted by API pricing. The official screens do not show this breakdown
Source: my own measurements (the last 7 days as of September 15, 2026; sessions run in the desktop app on Windows, computed with this article's aggregation script)
1. What the official screens show, and what they don't
Here are the main official places that report usage (as of September 2026). None of them shows each session's share. The closest is the plan breakdown in /usage, but its axis is which feature the usage went to, not which session used it.
| Where to look | What it shows | Share by session |
|---|---|---|
/usage, Session block | Token counts and estimated cost for the current session (by model). Resets to 0 with /clear. The documentation notes that it "is intended for API users," and that for Claude Max and Pro subscribers "the session cost figure isn't relevant for billing purposes" | ✕ Current session only |
/usage, plan usage breakdown (Pro, Max, Team, Enterprise) | Usage over the last 24 hours or 7 days, as percentages by skill, subagent, plugin and MCP server. Toggle with d and w. From v2.1.242 it also adds "a row for each of the heaviest /loop or other scheduled tasks that ran recently, ordered by total tokens" | ✕ By feature and by scheduled task, not by session |
| Desktop app usage ring | Context window usage for that session, and plan usage for the period. The documentation says "plan usage is shared across all your Claude Code surfaces" | ✕ Plan-wide figure |
| Settings > Usage on claude.ai | Progress bars for "your five-hour session and weekly usage limits," plus when each one resets | ✕ Plan-wide figure |
/insights | An HTML report analyzing recent sessions on this machine (which projects you work in and on what, where you got stuck). The documentation describes it as "a report on how you work rather than how many tokens you've used" | ✕ No token counts |
| Team and Enterprise analytics, Claude Console | Spend by user and by model | ✕ Per user |
| OpenTelemetry (exporting monitoring data) | Token and cost metrics carry the session ID by default | ✓ But you have to set up somewhere to collect it |
Source: Claude Code documentation, Manage costs effectively (/usage, /insights, organization dashboards), Claude Code documentation, Desktop (usage ring), Claude Help Center, Usage limit best practices (the usage page in Settings), Claude Code documentation, Monitoring (OpenTelemetry)
The official "session limit" is not about conversation sessions
On the claude.ai usage page, "session" means the five-hour usage window. It has nothing to do with the individual conversation sessions you open in Claude Code. Whenever this article says "session," it means the latter: each entry listed in the sidebar.
About the plan breakdown in /usage, the documentation says one more important thing: "The figures are approximate and computed from local session history on this machine, so usage from other devices or claude.ai is not included." In other words, even the official breakdown ultimately comes from your local logs. Read those same logs yourself and you can aggregate them along the axis the official screens leave out: the session. If you have already hit the limit and want to check what is left, see Claude Code "usage limit reached": causes and fixes.
2. The answer is in your local conversation logs
Claude Code saves the full record of every conversation as JSONL (one JSON object per line), one file per session. The documentation tells you where they live.
Everything goes in: messages, tool calls, even tool results. On Windows it is C:\Users\<username>\.claude\projects
Subagent conversations are kept in files separate from the main one. They are deleted together with the parent log when it ages out
Sessions used in the terminal are deleted once they pass cleanupPeriodDays (30 days by default). Sessions you started or most recently continued in the desktop app (or Cowork) are kept at any age from v2.1.248
Source: Claude Code documentation, Explore the .claude directory
On my machine, the <project> folder name was the absolute path of the working folder with its symbols replaced by - (D:\work\site-a becomes D--work-site-a). Sessions run from the Code tab of the desktop app were written to the same place, with "entrypoint":"claude-desktop" recorded on every line.
What you aggregate is the usage attached to the lines that record Claude's responses ("type":"assistant"). Here is one real line, reduced to just the fields the aggregation uses (IDs redacted).
{"type":"assistant","requestId":"req_…","timestamp":"2026-07-25T00:16:37.822Z",
"message":{"id":"msg_…","model":"claude-opus-5",
"content":[{"type":"text","text":"…"}],
"usage":{"input_tokens":2,"output_tokens":255,
"cache_read_input_tokens":33775,"cache_creation_input_tokens":17265,
"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":17265}}}}
The four numbers in usage (input, output, cache read, cache write) are official fields in Claude API responses. The format of the log file itself, meaning what gets written on each line, is not officially documented. The counting method in this article was checked against my own logs from v2.1.197 through v2.1.260, and it may stop working in future versions.
The documentation spells out one more caution: "Transcripts and history are not encrypted at rest. OS file permissions are the only protection." If a tool reads a .env file, its contents end up in the log too. Sharing aggregated results with other people is fine, but be careful when handing over the logs themselves or letting a tool you don't know well read them.
3. Add it up naively and you get about double: four pitfalls
Find the usage lines and add them all up. It is the most obvious approach, but in my logs the total came out at about twice the correct value. There are four reasons.
Pitfall 1: One response is written across several lines
One Claude response (one API request) is not necessarily one line in the log. On my machine, each content block, such as thinking, text or a tool call, was written as a line of its own (more than 99.9% of lines held exactly one block), and every one of those lines carried usage. What identifies a response is the pair of message.id and requestId.
71,865 responses. Adding these up is correct
69,810 responses. Adding them counts each twice
53,060 responses. Adding them counts each three times
19,475 responses. Adding them counts each four times or more
Source: my own measurements (logs from March 30 to September 15, 2026; 476,404 lines carrying usage amounted to 214,210 responses)
Of the 142,345 responses written across two or more lines, 76% had exactly the same usage on every line. In the remaining 24%, the output token value differed from line to line. So for each response, keep only the single line with the largest output token count. Separately, 1,903 responses also appeared in a different file (0.95% of all tokens). I haven't tracked down why, but collapsing everything on the same key keeps these from being counted twice as well.
Pitfall 2: Subagent records live in separate files
Read only the main .jsonl files and the subagents drop out entirely. In my logs, this is the share of the correct totals that came from subagents.
- Input (uncached): 42.9%
- Output: 23.2%
- Cache write: 10.8%
- Cache read: 6.9%
Per session the spread is even wider: over the last 7 days, weighted by price, it ran from sessions at 0% to a session at 54%. The more a session hands work out to subagents, the smaller it looks when you count only the main file.
Pitfall 3: The two errors partly cancel out, but differently in every session
Add up the lines in the main files as they are, and pitfall 1 inflates the count while pitfall 2 shrinks it. Over the last 7 days the total came to 2.06× the correct value. If every session were off by the same factor, the shares would still be right. In practice, they were not.
| Session | Correct share (rank) | Naive share (rank) | Naive ÷ correct | Subagent share |
|---|---|---|---|---|
| A | 31.7% (#1) | 27.4% (#1) | 1.79× | 21% |
| C | 12.7% (#2) | 12.0% (#3) | 1.96× | 2% |
| B | 11.4% (#3) | 15.0% (#2) | 2.71× | 9% |
| D | 8.6% (#4) | 5.4% (#5) | 1.30× | 39% |
| E | 5.4% (#5) | 6.8% (#4) | 2.60× | 39% |
| F | 4.6% (#6) | 4.1% (#8) | 1.85× | 0% |
| G | 3.2% (#9) | 4.7% (#6) | 3.02× | 5% |
Source: my own measurements (the last 7 days as of September 15, 2026. To make comparison easier, this table alone uses unweighted token counts, including for the subagent share. Session letters match the figure at the top)
Ranks 2 and 3 swapped, so did 4 and 5, and G, actually 9th, rose to 6th. D coming out small because it leans on subagents is exactly pitfall 2, but E, with the same 39% subagent share as D, went the other way at 2.60×. How many lines each response splits into (how much thinking and how many tool calls it contains) also plays a part, so you cannot predict the factor in advance. "Just divide by two" does not work.
Pitfall 4: 97% of the tokens are cache reads
Even once you count correctly, comparing raw token counts misjudges how heavy each session is. Here is what the last 7 days were made of.
Token breakdown (last 7 days, all sessions combined)
Source: my own measurements (the last 7 days as of September 15, 2026)
The unit prices, however, are nowhere near equal. On Anthropic's official pricing page, cache reads cost 0.1× the base input price (0.025× on Claude Fable 5.1 and Claude Mythos 5.1), 5-minute cache writes cost 1.25× and 1-hour cache writes 2×, and output is 5× the input price on every current model. Session C, which held 12.7% of the tokens, came to 10.4% once weighted by these prices.
The sheer size of the numbers tells you something too. Of the top 12 sessions, the 10 other than D and E read an average of roughly 410,000 to 480,000 tokens of context per response (D and E, which lean on subagents, averaged about 240,000 and 310,000). "Claude Code sends your full conversation with every request," so the longer a session stays open, the heavier each request becomes. Claude Code: What Is Actually Eating Your Context? explains how that works in detail.
4. The aggregation script
This aggregation script accounts for all four pitfalls. It runs on the Python 3 standard library alone (I tested it on 3.11). It only reads the logs and never modifies or sends anything. If you have moved your configuration directory with the CLAUDE_CONFIG_DIR environment variable, it reads from there instead.
import json, os, sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from pathlib import Path
DAYS = float(sys.argv[1]) if len(sys.argv) > 1 else 7
ROOT = Path(os.environ.get("CLAUDE_CONFIG_DIR") or Path.home() / ".claude") / "projects"
SINCE = datetime.now(timezone.utc) - timedelta(days=DAYS)
# USD per 1M input tokens (output = 5x). First match wins.
PRICES = [("sonnet-5", 2), ("sonnet", 3), ("haiku", 1), ("opus-4-1", 15),
("opus-4-2025", 15), ("opus", 5), ("fable", 10), ("mythos", 10)]
best = {} # one API response = one (message.id, requestId)
for path in ROOT.rglob("*.jsonl"): # also reads <session>/subagents/*.jsonl
project = path.relative_to(ROOT).parts[0]
with path.open(encoding="utf-8", errors="replace") as f:
for line in f:
if '"usage"' not in line:
continue
try:
row = json.loads(line)
except ValueError:
continue
msg = row.get("message") or {}
usage = msg.get("usage")
ts = row.get("timestamp")
if row.get("type") != "assistant" or not usage or not ts:
continue
if datetime.fromisoformat(ts.replace("Z", "+00:00")) < SINCE:
continue
key = (msg.get("id"), row.get("requestId"))
old = best.get(key)
if old is None or usage.get("output_tokens", 0) >= old[2].get("output_tokens", 0):
best[key] = (project, msg.get("model") or "", usage)
totals = defaultdict(lambda: [0, 0.0]) # [tokens, weight]
for project, model, u in best.values():
p = next((v for name, v in PRICES if name in model), 5)
read_rate = 0.025 if "5-1" in model and ("fable" in model or "mythos" in model) else 0.1
inp, out = u.get("input_tokens", 0), u.get("output_tokens", 0)
read, write = u.get("cache_read_input_tokens", 0), u.get("cache_creation_input_tokens", 0)
write_1h = (u.get("cache_creation") or {}).get("ephemeral_1h_input_tokens", 0)
totals[project][0] += inp + out + read + write
totals[project][1] += p * (inp + out * 5 + read * read_rate
+ (write - write_1h) * 1.25 + write_1h * 2)
all_tokens = sum(t for t, _ in totals.values()) or 1
all_weight = sum(w for _, w in totals.values()) or 1
print(f"last {DAYS:g} days: {len(best):,} responses")
print(f"{'weight':>7} {'tokens':>7} project")
for project, (t, w) in sorted(totals.items(), key=lambda kv: -kv[1][1]):
print(f"{w / all_weight:7.1%} {t / all_tokens:7.1%} {project}")
Save it under a name such as usage_by_session.py and pass the number of days as an argument. Leave it out and you get the last 7 days.
python usage_by_session.py # last 7 days
python usage_by_session.py 1 # last 24 hours
python usage_by_session.py 30 # last 30 days
The output looks like this (folder names redacted). weight is the price-weighted share and tokens is the share by raw token count.
last 7 days: 19,991 responses
weight tokens project
32.3% 31.7% D--work-project-a
11.1% 11.4% D--work-project-b
10.4% 12.7% D--work-project-c
7.7% 8.6% D--work-project-d
6.4% 5.4% D--work-project-e
What the script does
- It reads into subfolders with
rglob: it also picks up the files undersubagents/and adds them to the same project as their parent (pitfall 2) - It collapses lines into one response per
message.idandrequestIdpair, keeping the line with the most output tokens (pitfall 1) - It weights by unit price: the model's input price multiplied by 5× for output, 0.1× for cache reads, and 1.25× or 2× for cache writes (pitfall 4). The prices match the official pricing page as of September 2026, so update
PRICESwhen pricing changes - It groups by project folder: if you open several sessions in the same folder, aggregate by file name instead of
project(for subagents, by the name of the session folder one level abovesubagents) and the totals split by session ID
5. Measured: one of 29 sessions used nearly a third
Over the last 7 days, 29 sessions were active on my machine. As the figure at the top shows, usage was concentrated in just a handful of them.
One session, about a third of the total
Three sessions, more than half
The other 24 share about a third
More than half of the 29
Source: my own measurements (the last 7 days as of September 15, 2026, weighted by API pricing)
Laying the numbers side by side showed three things.
First, the sessions at the top are heavy on every single request. The top 3 read an average of roughly 410,000 to 480,000 tokens per response. It is not just that they made more requests: they stayed open all day while their context kept growing. The documentation also points out that in a session left open for a long time, even a one-line question incurs usage for the entire conversation.
Second, sessions that lean on subagents look small from the main conversation. For D and E, subagents accounted for 44% and 54% of price-weighted usage respectively. Looking at the main conversation in the sidebar, you never see that part.
Third, more than half of the sessions used almost nothing. Your limit does not drain faster because you have many sessions open; a few heavy sessions are what drain it. If you are going to act, starting with those few is enough. What to cut first is covered in Claude Code token-saving tips and the extra costs at the limit.
6. How to read the numbers, and where they stop
What this aggregation can and cannot tell you
- 🟡 The weights are an estimate based on API pricing. Anthropic has not published that Pro and Max limits drain in these proportions. They work as a yardstick for comparing sessions with one another, but not for calculating what percentage you have left
- It covers this machine only. Other machines, chats on claude.ai and sessions run in the cloud are not included. The official
/usagebreakdown has the same limitation - For sessions used in the terminal, you cannot go back further than 30 days. That is because the logs are deleted after
cleanupPeriodDays(30 days by default). Sessions you started or most recently continued in the desktop app (or Cowork) are kept at any age from v2.1.248 - 🟡 The log format is not an official specification. Details such as how many lines a single response splits into may change between versions. If the numbers look off, start by comparing the counts before and after collapsing duplicates
- Prices change. Claude Sonnet 5's introductory price of $2/$10 (input/output per million tokens) became its regular price (the increase planned for September 1 was called off), and Fable 5.1 cache reads cost 0.025× the input price. Keep
PRICESin step with the official pricing page
7. To keep watching from here on, use OpenTelemetry
The strength of aggregating logs is that you can see the past 30 days right away. If you instead want to keep watching from now on, OpenTelemetry, the official monitoring feature, is the better fit.
Once it is enabled, Claude Code exports a token metric, claude_code.token.usage (with the types input, output, cacheRead and cacheCreation), and a cost metric, claude_code.cost.usage. Both carry session.id by default (OTEL_METRICS_INCLUDE_SESSION_ID, default true). Because this does not read the log files, the duplicate lines from pitfall 1 never come into it, and subagent usage is recorded separately under query_source (main, subagent, auxiliary).
To try it locally first, start Claude Code with the exporter set to print to your terminal, as the documentation shows.
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
export OTEL_METRIC_EXPORT_INTERVAL=1000
claude
To aggregate continuously, set OTEL_METRICS_EXPORTER=otlp and send the data to a destination you run yourself (a monitoring backend that accepts OTLP). You have to set up that destination, and nothing from before you enable it gets recorded: those are the differences from aggregating logs.
Source: Claude Code documentation, Monitoring (metric names, attributes, environment variables)
FAQ
Q1. Isn't the plan breakdown in /usage enough?
It measures along a different axis. The plan breakdown shows, as percentages, which skills, subagents, plugins and MCP servers the usage went to, and it does not show which session used it. To track down what is draining your limit from the feature side, use /usage; from the session side, use the aggregation in this article.
Q2. Can I use an existing aggregation tool?
Yes. The unofficial tool ccusage, for example, can produce per-session reports from the same logs. Check two things before you use one. First, whether all processing stays on your machine: the logs contain tool results, unencrypted. Second, how it counts: to be sure it handles duplicate lines and subagents the same way, compare its output against this article's script once.
Q3. I want to include usage from other machines and from claude.ai.
Logs are only kept per machine, so you aggregate on each machine and add the results together. Chats on claude.ai are not recorded in Claude Code's logs. For how much of your plan is left overall, the progress bars under Settings > Usage on claude.ai are the reliable place to look.
Q4. I want to keep my logs so I can aggregate older usage too.
For sessions used in the terminal, raise cleanupPeriodDays in settings.json and they are kept longer. Sessions you started or most recently continued in the desktop app (or Cowork) are already kept at any age from v2.1.248 (to set a limit, use desktopSessionCleanupPeriodDays). Note, though, that the documentation lists lowering these values as a way to reduce how exposed your logs are. The longer you keep them, the longer unencrypted records sit on your machine, so bear that in mind.
Sources
- Claude Code Docs — Manage costs effectively (the Session block and plan usage breakdown in
/usage, the note that figures are computed from this machine's history,/insights, organization dashboards, why usage grows in long sessions) - Claude Code Docs — Explore the .claude directory (where logs are stored,
subagents/, the 30-day default forcleanupPeriodDaysand how desktop app sessions are handled, the lack of encryption) - Claude Code Docs — Desktop (the usage ring)
- Claude Help Center — Usage limit best practices (what Settings > Usage shows)
- Claude Code Docs — Monitoring (OpenTelemetry metric names,
session.id, environment variables) - Claude Platform Docs — Pricing (per-model prices, cache multipliers)
Related articles
- Claude Code: What Is Actually Eating Your Context? — why each response gets heavy
- Claude Code "usage limit reached": causes and fixes — when you hit the limit
- Claude Code token-saving tips and the extra costs at the limit — what to cut first
- Claude Code's weekly limit and early resets — how the weekly limit works