kibble

Kibble Blog

Where Claude Code and Codex keep their usage logs, and what a token count is worth

Claude Code and Codex keep local session records that include usage counters. It is not hidden and it is not encrypted; it is a directory of newline-delimited JSON in your home folder that the agent needs for resuming sessions. Everything a cost dashboard knows about local usage starts there, so it is worth knowing what is in those files, what is not, and how to turn a token count into a dollar figure that means something.

Claude Code: ~/.claude/projects/

Claude Code keeps one subdirectory per working directory it has been started in, named after the path with the slashes replaced, and inside it one .jsonl file per session, named by the session's UUID. Every line is one record: a user message, an assistant message, a tool result, a summary. The records that matter for cost are the assistant ones, and each of those carries the model that answered and the usage block the API returned for that call:

{
  "type": "assistant",
  "sessionId": "5b1c…",
  "cwd": "/Users/alice/work/acme-api",
  "timestamp": "2026-08-27T09:14:02.511Z",
  "message": {
    "model": "claude-sonnet-5",
    "usage": {
      "input_tokens": 412,
      "cache_creation_input_tokens": 18230,
      "cache_read_input_tokens": 96114,
      "output_tokens": 655
    },
    "content": [ …the reply, and any tool calls… ]
  }
}

Three things to notice. The usage is per API call, not per session, so summing the turns of a session counts the context window again on every turn. That is correct: it is what the vendor charges for. The cwd is a full path, which is how a tool can attribute tokens to a repository and also how it can leak a username and a client's name if it is careless. And the content is right there beside the numbers, which is why a collector's promise to not read it has to be a property of its code rather than its intentions.

Claude Code deletes transcripts older than a retention window (cleanupPeriodDays in its settings, 30 days by default). A usage tool that only runs when someone remembers will find that the second half of last quarter is gone. Anything that reads these files for cost has to run on a schedule, and a dashboard cannot backfill a month the agent already cleaned up.

Codex: ~/.codex/sessions/

OpenAI's Codex CLI writes a rollout file per session, filed by date: ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl. The shape is the same idea, one JSON record per line, with the session's metadata first (including the working directory) and then the conversation. Token counts arrive as their own event type rather than on each message, and they are recorded as running totals for the session, so the naive sum of every count event overstates the session several times over. A correct reader takes the last total per session, or diffs consecutive events, and prices the difference. Inside a live session, /status shows the session's token usage and /usage the account's limits; the rollout file is what remains once the session ends.

Models are named in the session metadata and on the turn. Codex's cache accounting is coarser than Claude Code's, which is one reason a Codex estimate and a Claude Code estimate for the same afternoon should not be compared to the cent.

GitHub Copilot: ~/.copilot/session-state/ and the VS Code user-data directory

Copilot CLI writes one event log per session at ~/.copilot/session-state/<session-id>/events.jsonl, and its durable token counters arrive on the session.shutdown event as a cumulative per-model snapshot, the same running-total trap as Codex with one more twist: Copilot's input count includes its cache reads and writes, so those are subtracted before pricing. Copilot Chat in VS Code keeps each session under the editor's user-data directory, as a JSON snapshot in older builds and a JSONL mutation log in current ones, with whole-turn totals per model when the editor saved them and only the last model call when it did not. The Copilot log guide walks both formats field by field, and the tracking guide says what a team can do with the counts.

Other agents

Gemini CLI, OpenCode, Amp and the rest all do a version of the same thing, each in its own directory with its own field names. Parsing them is a moving target; the projects that do it well (Kibble's collector uses the open-source tokscale core for exactly this, behind an adapter so the parser can be swapped) treat every agent's format as something that will change with the next release, and measure their output against the raw transcripts rather than trusting the parser.

What a token is worth

Price token categories separately. The following ratios illustrate standard Anthropic pricing for models such as Sonnet 5; they are not universal across models or providers. Check the model and cache-write duration against the vendor rate table.

FieldWhat it isRelative rate
input_tokensFresh prompt tokens the model read for the first time1×
output_tokensWhat the model wrote, including any reasoning tokens≈5× input
cache_creation_input_tokensPrompt tokens written into the five-minute prompt cache1.25× input
cache_read_input_tokensPrompt tokens served from the cache0.1× input

A coding agent re-sends the whole conversation on every turn, so after the first few turns nearly all of its input is a cache read. In the record above, cache reads are 83% of the tokens and about a quarter of the cost; the 18,230 tokens written to the cache cost more than twice as much as the 96,114 read from it. Two consequences:

Kibble's collector prices each field at the vendor's current list rate, keeps the result as an integer number of micro-dollars (a float would drift over a month of additions), and calls the result what it is: an estimate. What the vendor actually charged is a different number, from a different source, and the two are never added together.

What to leave on the machine

Here are examples of Kibble's usage fields and the content boundary. The privacy policy is the authoritative field list, including capability names and billing-plan metadata. The server rejects unexpected fields.

SentNever sent
The day, the agent, the modelPrompts, replies, tool results
Input, output, cache read, cache write totals, message countFile contents, diffs
Opaque session ids, for de-duplication and size percentilesTool arguments
The repository's name (acme-api, or owner/repo from the git remote)The path, the username in it, the hostname, any hardware id

The repo name is the one place a path is touched, so it gets its own function: take the checkout's directory name or the remote's owner/repo, fold worktrees into their main repo, and throw away everything above it. A monorepo package resolves to the monorepo. /Users/alice/clients/acme-bank/api leaves the machine as api, or as acme/api if that is what the remote says, and Alice's name and her client list stay where they were.

The numbers are in the same file as the words. A collector's privacy is the part of its code that reads one and not the other.