Analyze Your Session Costs
Parse Claude Code session files to extract token usage, cache efficiency, and cost breakdowns with reproducible scripts.
Analyze Your Session Costs
Every Claude Code session is logged as a JSONL file. You can parse it to measure token usage, cache efficiency, cost per phase, and compare what a session cost versus what it would have cost without caching.
Where Sessions Live
~/.claude/projects/{project-scope}/{session-id}.jsonlEach session gets a UUID filename. The project scope is derived from your working directory path (dashes replace slashes). To find your most recent session:
ls -lt ~/.claude/projects/$(pwd | sed 's|/|-|g; s|^-||')/\*.jsonl | head -3JSONL Structure
Each line is a JSON object with a type field:
| Type | What it contains |
|---|---|
assistant | Claude's responses, tool calls, and message.usage with token counts |
user | Your messages and tool results |
system | System messages |
permission-mode | Permission mode changes |
The key data is on assistant messages in message.usage:
{
"type": "assistant",
"timestamp": "2026-04-03T01:15:22.496Z",
"message": {
"usage": {
"input_tokens": 3,
"output_tokens": 497,
"cache_read_input_tokens": 146296,
"cache_creation_input_tokens": 668
},
"content": [
{ "type": "text", "text": "..." },
{ "type": "tool_use", "name": "Read", "input": { "file_path": "/path/to/file" } }
]
}
}Quick Cost Check
Run this against any session file for an instant summary:
node -e '
const lines = require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n");
let input=0, output=0, cacheRead=0, cacheWrite=0, turns=0;
for (const line of lines) {
try {
const u = JSON.parse(line).message?.usage;
if (u) {
input += u.input_tokens || 0;
output += u.output_tokens || 0;
cacheRead += u.cache_read_input_tokens || 0;
cacheWrite += u.cache_creation_input_tokens || 0;
turns++;
}
} catch {}
}
const cost = (input/1e6)*5 + (output/1e6)*25 + (cacheRead/1e6)*0.50 + (cacheWrite/1e6)*6.25;
const uncached = ((input+cacheRead+cacheWrite)/1e6)*5 + (output/1e6)*25;
const hitRate = (cacheRead/(input+cacheRead+cacheWrite)*100).toFixed(1);
console.log("API turns: ", turns);
console.log("Input: ", input.toLocaleString(), "tokens");
console.log("Output: ", output.toLocaleString(), "tokens");
console.log("Cache read: ", cacheRead.toLocaleString(), "tokens");
console.log("Cache write: ", cacheWrite.toLocaleString(), "tokens");
console.log("Cache hit rate:", hitRate + "%");
console.log("Cost: $" + cost.toFixed(2));
console.log("Without cache: $" + uncached.toFixed(2));
console.log("Savings: $" + (uncached-cost).toFixed(2), "(" + (((uncached-cost)/uncached)*100).toFixed(1) + "%)");
' ~/.claude/projects/.../SESSION_ID.jsonlReplace the path with your actual session file.
Pricing Reference (2026)
| Model | Input | Output | Cache Write (5m) | Cache Read |
|---|---|---|---|---|
| Opus 4.6 | $5/MTok | $25/MTok | $6.25/MTok | $0.50/MTok |
| Sonnet 4.6 | $3/MTok | $15/MTok | $3.75/MTok | $0.30/MTok |
| Haiku 4.5 | $1/MTok | $5/MTok | $1.25/MTok | $0.10/MTok |
Cache read is always 0.1x the base input price — a 90% discount. Cache write (5-minute) is 1.25x the base input price. This means caching pays for itself after just one cache read.
Claude Code subscription pricing differs from raw API rates. The session JSONL records actual token counts — apply the API rates above to calculate what the session would cost at API pricing. The cache hit ratio is the metric that transfers regardless of pricing plan.
Detailed Analysis: Tool Usage
Extract what tools were used and how many times:
node -e '
const lines = require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n");
const tools = {};
const files = { read: new Set(), write: new Set(), edit: new Set() };
let writeLines = 0, editLines = 0;
for (const line of lines) {
try {
const obj = JSON.parse(line);
if (obj.type !== "assistant" || !Array.isArray(obj.message?.content)) continue;
for (const block of obj.message.content) {
if (block.type !== "tool_use") continue;
tools[block.name] = (tools[block.name] || 0) + 1;
if (block.name === "Read") files.read.add(block.input?.file_path);
if (block.name === "Write") {
files.write.add(block.input?.file_path);
writeLines += (block.input?.content || "").split("\\n").length;
}
if (block.name === "Edit") {
files.edit.add(block.input?.file_path);
editLines += (block.input?.new_string || "").split("\\n").length;
}
}
} catch {}
}
console.log("\\nTool usage:");
Object.entries(tools).sort((a,b) => b[1]-a[1]).forEach(([k,v]) => console.log(" ", k + ":", v));
console.log("\\nFiles:");
console.log(" Read:", files.read.size, "unique");
console.log(" Write:", files.write.size, "unique (" + writeLines + " lines)");
console.log(" Edit:", files.edit.size, "unique (" + editLines + " lines)");
console.log(" Total lines:", writeLines + editLines);
' ~/.claude/projects/.../SESSION_ID.jsonlPhase Analysis
To break a session into phases, filter by timestamp ranges. Find phase boundaries by searching for landmarks (blueprint invocations, task creation, etc.):
node -e '
const lines = require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").map(l => JSON.parse(l));
// Opus 4.6 pricing
const P = { input: 5, output: 25, cacheRead: 0.50, cacheWrite: 6.25 };
function analyzeWindow(start, end, label) {
const window = lines.filter(o => o.timestamp >= start && o.timestamp < end);
const turns = window.filter(o => o.type === "assistant" && o.message?.usage);
let i=0, o=0, cr=0, cw=0;
turns.forEach(t => {
const u = t.message.usage;
i += u.input_tokens || 0;
o += u.output_tokens || 0;
cr += u.cache_read_input_tokens || 0;
cw += u.cache_creation_input_tokens || 0;
});
const cost = (i/1e6)*P.input + (o/1e6)*P.output + (cr/1e6)*P.cacheRead + (cw/1e6)*P.cacheWrite;
const hit = (cr/(i+cr+cw)*100).toFixed(1);
const mins = ((new Date(end) - new Date(start)) / 60000).toFixed(1);
console.log(label + ": " + mins + "min, " + turns.length + " turns, $" + cost.toFixed(2) + ", " + hit + "% cached");
}
// Edit these timestamps to match your session phases
analyzeWindow("2026-04-03T01:00:00Z", "2026-04-03T01:21:00Z", "Blueprint");
analyzeWindow("2026-04-03T01:21:00Z", "2026-04-03T01:31:00Z", "Implementation");
' ~/.claude/projects/.../SESSION_ID.jsonlFind your phase boundaries by grepping for tool names:
# Find when blueprint started (first graph query)
node -e '...' | grep composure-graph | head -1
# Find when implementation started (first TaskCreate)
node -e '...' | grep TaskCreate | head -1
# Find when fixes started (edits after task completion)
node -e '...' | grep TaskUpdate.*completed | tail -1What the Numbers Mean
Cache Hit Rate
The percentage of input tokens served from cache. Higher is better — it means more context is being reused across turns instead of re-processed.
| Rate | What it means |
|---|---|
| < 80% | Short session or lots of agent spawns fragmenting context |
| 80-95% | Normal session with steady context building |
| 95-99% | Long session riding warm context — peak efficiency |
| > 99% | Implementation phase after heavy planning — context is fully warm |
Cost per Line
Divide total cost by total lines written (Write + Edit). Includes all planning, reading, and thinking — not just the write operations.
| Range | Context |
|---|---|
| < $0.01/line | High-volume edits (find-and-replace patterns, CTA swaps) |
| $0.01-0.05/line | Typical implementation with planning overhead |
| $0.05-0.15/line | Complex architecture with heavy graph analysis |
| > $0.15/line | Research-heavy session or lots of scope changes |
Agent Spawn Impact
Each agent spawn creates a fresh context that cannot reuse the parent session's cache. Compare:
| Direct Read | Agent Spawn | |
|---|---|---|
| Per-file cost | ~$0.005 (cache rate) | ~$0.15+ (fresh context) |
| Cache sharing | Yes — cached for all future turns | No — ephemeral context |
| Best for | Code work, refactors, implementations | Research, doc generation, parallel summaries |
Example Output
From the User Dashboard in 30 Minutes session:
API turns: 253
Input: 1,743 tokens
Output: 54,414 tokens
Cache read: 25,354,609 tokens
Cache write: 418,633 tokens
Cache hit rate: 98.5%
Cost: $16.66
Without cache: $130.23
Savings: $113.57 (87.2%)98.5% of every API call's input was served from cache at $0.50/MTok instead of $5.00/MTok — the session effectively ran at one-tenth of the uncached cost for input tokens.