Tracking Codex CLI costs across projects

Codex writes enough to its session files to rebuild a cost report. Here is how to do it, and where that method quietly falls apart.

Short answer: Codex CLI does not ship a cost dashboard, but it does write a session log per run on your machine, under the Codex home directory (normally ~/.codex, with session transcripts as newline-delimited JSON). Those transcripts carry token usage events and the working directory the session started in, which is enough to group spend by project and multiply tokens by your model rates. Community readers such as ccusage grew out of exactly this trick on the Claude Code side and have been extending toward Codex. If you would rather not parse anything, a local proxy in front of the CLI records every call as it happens, which is the route I took with Probe0.

The catch worth knowing before you start: if you signed into Codex with your ChatGPT plan rather than an API key, there is no dollar amount to compute. You are consuming rate-limit windows, not billed tokens, and any tool that prints a dollar figure for that mode is estimating what the same work would have cost on the API. That number is useful for deciding whether to stay on a plan, and misleading if you treat it as an invoice.

What Codex actually leaves on disk

Codex CLI keeps its state in a home directory you can relocate with the CODEX_HOME environment variable. Inside it you get configuration (config.toml), authentication material, and a sessions tree containing one rollout file per run, organised by date. Each rollout file is a stream of JSON lines: the initial metadata record, user messages, assistant messages, tool calls, and usage events emitted as the turn completes.

The fields you care about are the usage counters attached to those events. Codex reports input tokens, cached input tokens, output tokens, and reasoning tokens separately, and it reports both the last turn and a running total for the session. Do not hardcode the exact key names from a blog post, including this one. The schema has moved between releases and the practical approach is to open one recent rollout file, look at what your installed version writes, and pin your parser to that.

Two details make per-project attribution possible at all. The session metadata records the current working directory at launch, so a session maps to a repository without any tagging on your part. And Git-aware metadata is often present too, which means you can slice by branch if your team works in worktrees.

Building a per-project report from the logs

The whole job is a fold over files. A short script gets you most of the way:

  • Walk the sessions directory and read each rollout file line by line rather than loading it whole. Long agent runs produce large files and a naive JSON.parse of the entire file will not work anyway, since these are JSONL.
  • Pull the working directory and the model name from the first few records, and keep them as the session key.
  • Sum the usage events, but take the running total from the final usage event instead of adding every per-turn number. Codex reports cumulative totals alongside per-turn ones, and double counting is the single most common mistake in homegrown readers.
  • Treat cached input tokens as their own bucket. Cached reads are billed far below fresh input on OpenAI's API, so folding them into one input number overstates cost on long agent sessions, which are mostly cache reads by volume.
  • Multiply by rates you looked up today, from the provider's own pricing page, stored in a config file rather than inline in the script. Model prices change and a stale constant silently poisons every historical report.
  • Group by directory, then roll up by week. Per-session numbers are noisy; the useful signal is which repository is drifting upward month over month.

Reconcile the result against your provider dashboard once before you trust it. If the two disagree by more than a few percent, the usual culprits are reasoning tokens (billed as output), cached input priced at the wrong tier, or sessions you resumed, which can produce a second rollout file that repeats context.

Where the log-parsing approach runs out

It is a good method and I still use it as a cross-check. But it has three structural gaps.

It is retrospective. You learn that a run cost too much after the run finished. An agent that loops on a failing test does not stop because a report will look bad tomorrow.

It is per-tool. Parse Codex rollouts and you have Codex. If Claude Code and Cursor also run on the same laptop, each needs its own reader, its own schema assumptions, and its own breakage when the vendor changes a field. Most developers I talk to run at least two agents, and the number they actually want is the total.

It cannot see intent. A rollout file tells you tokens were spent. It cannot tell you the same prompt was answered three minutes earlier, or that a small model would have handled it, because by the time the file exists the money is gone.

The proxy view, and what Probe0 does with it

I built Probe0 because I wanted one ledger instead of three parsers. It is a proxy that runs on your machine. You start it once and install a certificate once, and every coding agent CLI on the box routes through it: Codex, Claude Code, Cursor. There is no per-tool configuration and no Probe0 server anywhere. Nothing leaves your laptop that was not already going to the model provider.

Recording is the module that replaces the parsing script. It writes a full request ledger: model, token counts, real cost, latency, and which process made each call. Because the proxy sees the process, attribution comes from what actually ran rather than from a directory field a vendor might rename.

Sitting at that point in the path also makes intervention possible rather than just observation. Spend Guard enforces a hard cap per run and per day: it warns first, then pauses, which is the difference between reading about a runaway loop and stopping one. Exact Cache answers repeated identical calls from local disk without touching the network. Semantic Cache matches near-identical prompts from a local vector index behind a strict similarity floor, and refuses to match anything carrying tool calls, because a wrong hit inside an agent run corrupts the run and no cache saving is worth that. Request Coalescing collapses simultaneous identical calls into one upstream request. Model Tiering tries a cheaper model first. Local Routing sends work to a model already loaded in Ollama or LM Studio and retries weak answers on the cloud. Each module switches off independently and each reports what it saved, so you can judge them one at a time instead of taking a bundled claim on faith.

The plan-tier question falls out of the same ledger. Once there are a few weeks of real usage recorded, Probe0 can tell you when your consumption sits below the tier you are paying for. That is a boring feature that has paid for itself more than once.

Honest limits

Probe0 is macOS only and in private beta. Sign-in is Google or GitHub, and the account system is new. It is a single-developer tool, not a team gateway: there is no shared deployment, no org-level policy, no multi-user dashboard. If you need spend attribution across twenty engineers, a hosted gateway or an observability platform is the right shape and I would point you there. Provider coverage is what coding agents actually call, not a hundred-provider catalog.

And the log-parsing route remains genuinely good if you only use Codex, only want a monthly number, and do not want another process on your machine. Tools in that family are free, open source, and require nothing to be installed in your network path. The case for a proxy starts when you run more than one agent, or when you want the cap to actually stop something.

A cost report tells you what happened. A proxy is the only place where you can still change what happens.

Related