kibble

Kibble Blog

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

Every coding agent that runs on a laptop already keeps a record of what it spent. 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.

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.

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

A model's list price has four rates, and the one people forget is the one that dominates a coding agent's bill. Per million tokens, roughly:

FieldWhat it isRelative rate
input_tokensFresh prompt tokens the model read for the first time
output_tokensWhat the model wrote, including any reasoning tokens≈5× input
cache_creation_input_tokensPrompt tokens written into the 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

Given everything that is in those files, here is the line Kibble draws, field by field. It is the whole list; the server rejects anything not on it.

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.