ompsure

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}.jsonl

Each 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 -3

JSONL Structure

Each line is a JSON object with a type field:

TypeWhat it contains
assistantClaude's responses, tool calls, and message.usage with token counts
userYour messages and tool results
systemSystem messages
permission-modePermission 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.jsonl

Replace the path with your actual session file.

Pricing Reference (2026)

ModelInputOutputCache 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.jsonl

Phase 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.jsonl

Find 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 -1

What 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.

RateWhat 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.

RangeContext
< $0.01/lineHigh-volume edits (find-and-replace patterns, CTA swaps)
$0.01-0.05/lineTypical implementation with planning overhead
$0.05-0.15/lineComplex architecture with heavy graph analysis
> $0.15/lineResearch-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 ReadAgent Spawn
Per-file cost~$0.005 (cache rate)~$0.15+ (fresh context)
Cache sharingYes — cached for all future turnsNo — ephemeral context
Best forCode work, refactors, implementationsResearch, 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.

On this page