DEV Community

Cover image for Claude Code, Codex and Gemini CLI Just Started Speaking the Same Cost Language
Andre Burgstahler
Andre Burgstahler

Posted on Originally published at Medium

Claude Code, Codex and Gemini CLI Just Started Speaking the Same Cost Language

All three now export OpenTelemetry. One local collector and a short script turn it into a single cost ledger, once you fix three fields.

Abstract illustration: three thin streams of small glowing tokens in teal, amber and slate flow from the left into a single teal funnel, which feeds a neat stack of off-white receipt cards on a dark navy background
Three streams, one funnel, one stack of receipts.

TL;DR

  • Between 13 and 15 October, Claude Code, Codex and Gemini CLI each shipped a telemetry change that makes plain OpenTelemetry (OTel) the practical way to get usage data out of them. The Claude Platform added OTel export for the API in the same week.
  • Point all three at one local collector, keep a single receipts file, and stop parsing each tool's private session logs.
  • "Same language" is generous: they share the transport, not the vocabulary. Each tool counts cached input differently, and only Claude Code reports a cost. A 93-line script normalises the three into one table.

If you use more than one coding agent, you've probably tried to answer a simple question: what did this week cost, and where did it go?

The usual answer is to parse local logs. This week's r/ClaudeCode thread on Claude Code usage shared a prompt that walks every JSONL file under ~/.claude/projects and sums the usage fields. It also carries a warning that says a lot about this approach: "Count each message.id once, because one message is logged on several lines."

That works until the file format changes, and you'd need a second parser for Codex and a third for Gemini CLI. In mid-October all three tools moved the other way.

Three days, four releases

Timeline of releases, 13 to 15 October 2026. 13 Oct: Gemini CLI v0.65.0-preview.0 adds custom OTLP headers. 14 Oct: Claude Code v2.1.300 splits cache writes into 5-minute and 1-hour fields and adds GenAI attributes to trace spans. 14 Oct: Claude Platform launches OpenTelemetry export in beta. 15 Oct: Codex 0.163.0 keeps custom OTLP metrics exporters on when analytics is disabled and counts AGENTS.md edits.
Each release moves one tool's usage data toward plain OpenTelemetry.

  • Gemini CLI v0.65.0-preview.0 (13 Oct) added custom OTLP headers (PR #29641): telemetry.otlpHeaders in settings, or GEMINI_TELEMETRY_OTLP_HEADERS. That's what you need to send to an authenticated collector or vendor.
  • Claude Code v2.1.300 (14 Oct) added cache_creation_5m_tokens and cache_creation_1h_tokens to the api_request event, "splitting cache writes by lifetime." It also added GenAI semantic-convention attributes to trace spans, such as gen_ai.provider.name and gen_ai.conversation.id.
  • The Claude Platform (14 Oct) launched OpenTelemetry export in beta for Messages API requests and Claude Managed Agents sessions.
  • Codex 0.163.0 (15 Oct) now honours custom OTLP metrics exporters when analytics is disabled (#52278). Before, turning analytics off also turned off your own metrics exporter. It also records telemetry when apply_patch edits AGENTS.md (#51652).

None of these is big on its own. Together they mean every major coding CLI can send usage data to an endpoint you choose, in a format you don't have to reverse-engineer.

Receipts, not diaries

A tool's local session log is a diary. It's written for the tool, its format can change in any release, and nobody promises it'll stay the same. The OTel events are receipts. They're documented, they're versioned in the release notes, and they're meant to leave the machine.

Parse the receipt, not the diary.

Using receipts also means one pipeline. You don't need three parsers. You need one collector that receives all three tools, and one file you can query.

Diagram: Claude Code, Codex and Gemini CLI on the left each send OTLP to an otelcol-contrib collector in the middle. The collector has an OTLP receiver on 127.0.0.1:4317 (gRPC) and :4318 (HTTP), an attributes processor that deletes prompt, response_text and user.email, and a file exporter writing receipts.jsonl. On the right, ledger.py turns the file into one row per tool and model; later the same collector can feed Prometheus, Grafana or a vendor.
Everything runs on localhost. Nothing leaves your machine unless you add an exporter that sends it.

Step 1: run one collector

Download the OpenTelemetry Collector's contrib build (pick the tarball for your OS from the release page):

V=0.163.1
curl -LO https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v$V/otelcol-contrib_${V}_linux_amd64.tar.gz
tar xzf otelcol-contrib_${V}_linux_amd64.tar.gz otelcol-contrib
./otelcol-contrib --config otel-ledger.yaml
Enter fullscreen mode Exit fullscreen mode

And this is the whole config:

https://gist.github.com/br4vesirrobin/be88305859213211e54003dc8cb0f0ca

It listens only on localhost, writes one JSON line per batch, and deletes prompt text and email addresses before anything is written. otelcol-contrib validate --config otel-ledger.yaml checks it without starting anything.

Step 2: point each tool at it

Tool Where it goes Switch Prompt text by default
Claude Code Shell, or env in ~/.claude/settings.json CLAUDE_CODE_ENABLE_TELEMETRY=1 plus OTEL_LOGS_EXPORTER=otlp Redacted
Codex [otel] in ~/.codex/config.toml exporter and metrics_exporter set to otlp-grpc or otlp-http Off
Gemini CLI telemetry in ~/.gemini/settings.json "enabled": true Logged, so set "logPrompts": false

Claude Code reads the standard OTel variables:

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317"
  }
}
Enter fullscreen mode Exit fullscreen mode

Put this in ~/.claude/settings.json, or export the same variables in your shell. Claude Code ignores these variables in a repository's .claude/settings.json, "so a repository can't use them to turn telemetry on, choose where it goes, or capture content." That's the right default: a repo you just cloned shouldn't be able to redirect your telemetry.

Codex uses an [otel] table. The exporter setting sends log events, which is where the per-call token counts are. metrics_exporter sends counters:

# ~/.codex/config.toml
[otel]
environment = "dev"
log_user_prompt = false                      # the default; keep prompts out of the ledger
exporter = { otlp-grpc = { endpoint = "http://localhost:4317" } }          # logs: per-call token events
metrics_exporter = { otlp-grpc = { endpoint = "http://localhost:4317" } }  # counters such as codex.turn.token_usage
Enter fullscreen mode Exit fullscreen mode

I validated that block against the config schema shipped with Codex 0.163.0. If you use otlp-http instead, it requires a protocol (binary or json). One docs gap: the config reference still says analytics.enabled = false disables custom OTLP metrics exporters too, which 0.163.0 changed. The release notes are the newer source.

Gemini CLI keeps it in settings:

{
  "telemetry": {
    "enabled": true,
    "target": "local",
    "otlpEndpoint": "http://localhost:4317",
    "otlpProtocol": "grpc",
    "logPrompts": false
  }
}
Enter fullscreen mode Exit fullscreen mode

logPrompts defaults to true, so leave that line in. The collector's redact step is a second net, not the first.

Step 3: the part that isn't the same language yet

Here's the honest catch. The three tools now share the transport, but they don't count tokens the same way. If you add their "input" numbers together you get nonsense.

Three identical stacked bars of one model call: uncached input, cache write, cache read, output and reasoning. Under each, lines show what that tool's fields cover. Claude Code: input_tokens is uncached input only, with separate cache_creation and cache_read fields; output_tokens includes thinking; cost_usd included. Codex: input_token_count includes cached reads and cache writes; output includes reasoning; no cost. Gemini CLI: input includes cached content; output excludes thoughts; no cost.
Normalise before you add.

  • Claude Code's input_tokens excludes cache reads and cache writes. Its own docs say so, and they point out that the GenAI conventions expect input to include them. output_tokens already includes thinking.
  • Codex reports input_token_count with cached tokens inside it. Codex's own code computes uncached input as input minus cached, and its usage parser takes cache writes from the same input details. output_token_count includes reasoning.
  • Gemini CLI sets input_token_count from the Gemini API's promptTokenCount, which "includes the number of tokens in the cached content." Its output_token_count doesn't include thoughts; those are a separate thoughts_token_count.

Then there's cost. Only Claude Code puts a cost_usd on each request. Codex and Gemini CLI give you tokens, and pricing them is your job. The community Grafana dashboard that covers all three tools has the same limitation: its Codex and Gemini cost panels need prices added at the collector.

The new gen_ai.* attributes help with naming and correlating traces, but they don't settle this. Claude Code's docs state plainly that it "doesn't set gen_ai.usage.* attributes." So for now, one receipt format is something you build at the edge, and that's a short script.

Step 4: the ledger

ledger.py reads the collector's file, picks out one event per model call from each tool, normalises the fields, and prints one row per tool and model. It needs Python 3.9 or later and nothing else:

https://gist.github.com/br4vesirrobin/0c35d764cd68ef24deed2c249edbd692

Run python3 ledger.py receipts.jsonl. I tested the collector and script on 21 October with otelcol-contrib 0.163.1, sending synthetic events shaped like each tool's documented events, over both gRPC and HTTP. This is the output from those test events:

tool         model                  calls     input    c.read  c.write   of 1h   output    cost $
claude-code  claude-sonnet-5            2      1500     86000     6000    4000      950    0.0884
codex        example-codex-model        1      4000     40000     8000       0     2100      n/a*
gemini-cli   example-gemini-model       1      6000     24000        0       0     1500      n/a*
* some calls have no cost: add the model to --prices
Enter fullscreen mode Exit fullscreen mode

Every column now means the same thing for every tool. input is uncached input, c.read and c.write are cache traffic, and output includes reasoning. The prompt text and email address I put in the test events weren't in the file.

For Codex and Gemini CLI, --prices prices.json takes your own per-million-token rates, keyed by model name as it appears in the ledger, with input, output and optional cache_read and cache_write. The script never guesses a price. Add --csv to get a spreadsheet.

What the 5m/1h split buys you

If you read Compact, Clear or Hand Off?, you know cache lifetime decides what a long session costs. The v2.1.300 split is what makes that visible per request.

Bar chart of Anthropic prompt-caching price multipliers relative to base input: cache read 0.1x, base input 1x, 5-minute cache write 1.25x, 1-hour cache write 2x, which is 1.6 times a 5-minute write. Note: with the old single cache_creation_tokens field, 10,000 written tokens could mean 12,500 or 20,000 tokens' worth of base-input cost.
Same token count, different price, and now a field that tells you which.

Anthropic's prompt-caching docs price a 5-minute cache write at 1.25 times base input and a 1-hour write at 2 times. Before 14 October, Claude Code's telemetry reported one cache_creation_tokens number, so the same count could be worth very different amounts. The ledger shows the 1-hour share as its own column.

This is the same lesson as Where Did My Claude Usage Go? and Cut Your Claude Code Bill by Up to 95%: the bill is mostly cache traffic, and you can't tune what you can't see.

A receipt for AGENTS.md churn

One small addition in Codex 0.163.0 deserves a mention. Each committed apply_patch change to AGENTS.md or AGENTS.override.md now increments a counter, codex.agents_md.edit, tagged with the file name. It's a metric, so it reaches the collector through metrics_exporter.

In How Much Should You Invest in Your AGENTS.md? I argued that instruction files should change on purpose, not by drift. This counter gives you a receipt for that. If the agent is editing its own instructions several times a day, you'll see it next to the token counts.

For teams: the same collector, one hop further

Everything above runs on one laptop. For a team, the same config works with two changes. The receiver listens on a shared address with TLS, and each developer's tool sends a header, which is now possible in all three: OTEL_EXPORTER_OTLP_HEADERS for Claude Code, headers inside the Codex exporter, and otlpHeaders for Gemini CLI. Tag each developer or team with resource attributes. Claude Code documents OTEL_RESOURCE_ATTRIBUTES for exactly this.

API workloads are covered too, now. The Claude Platform's OTel export beta sends spans for Messages API requests and Managed Agents sessions to a destination you add in the Claude Console, so it needs an endpoint the platform can reach, not localhost.

Checklist

  1. Run one collector on localhost with a file exporter. Validate the config before you trust it.
  2. Turn on each tool with the snippets above, in user-level config.
  3. Set Gemini's logPrompts to false. Keep the collector's redact step anyway.
  4. Normalise before you add. Uncached input, cache read, cache write, and output including reasoning.
  5. Price what isn't priced with your own rate table. Never let a script guess.
  6. Watch the 1-hour column. If it's large and your sessions are short, you're paying for a lifetime you don't use.
  7. Watch codex.agents_md.edit if Codex works in repos with an AGENTS.md.
  8. Stop parsing session logs for cost. Keep them for debugging.

Threads that prompted this

  • Claude Code usage speculation (r/ClaudeCode, 21 Oct): the JSONL-parsing prompt quoted above. It's careful work, and a good illustration of why a documented event beats a private log.
  • Created a tool to keep tabs on my agent on the fly (r/ClaudeCode, 21 Oct): "I also used Codex and Gemini for my workflow, there was no great tool which allowed me to keep tabs on all my agents together." That's the multi-tool problem in one sentence.
  • Does claude -p consume subscription usage at the same rate? (r/ClaudeCode, 21 Oct): it asks whether faster depletion comes from "extra harness prompts/tools, more requests, or cache misses." Per-request cache receipts are how you'd start to tell those apart.

Sources

Tags: OpenTelemetry, Claude Code, OpenAI Codex, Gemini CLI, AI Coding


If this was useful, subscribe to Agents in Practice for one practical note on AI coding agents a week. Helping your team adopt Claude Code or Codex safely and without the surprise bill? I consult on exactly that: andre-burgstahler-9n5.pages.dev.

This article was written with AI assistance and reviewed by the author.

Top comments (0)