cd /news/ai-agents/claude-code-codex-and-gemini-cli-jus… · home › topics › ai-agents › article
[ARTICLE · art-160285] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

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

Between 13 and 15 October, Anthropic's Claude Code, OpenAI's Codex and Google's Gemini CLI each shipped telemetry changes that let them export usage data via plain OpenTelemetry, with the Claude Platform adding OTel export for its API the same week. A developer demonstrated routing all three tools into a single local OTel Collector that writes one JSON-line receipts file, using a 93-line script to normalize their differing cache-input accounting and cost reporting into one table. The writeup notes the tools share the transport but not the vocabulary: only Claude Code reports cost, and each counts cached input differently.

by read8 min views6 publishedOct 29, 2026

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.

Each release moves one tool's usage data toward plain OpenTelemetry.

telemetry.otlpHeaders in settings, or GEMINI_TELEMETRY_OTLP_HEADERS. That's what you need to send to an authenticated collector or vendor.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. apply_patch edits AGENTS.md ( 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.

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.

Everything runs on localhost. Nothing leaves your machine unless you add an exporter that sends it.

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

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.

Tool Where it goes Switch Prompt text by default
Claude Code Shell, or env in~/.claude/settings.json CLAUDE_CODE_ENABLE_TELEMETRY=1 plusOTEL_LOGS_EXPORTER=otlp Redacted
Codex [otel] in~/.codex/config.toml exporter andmetrics_exporter set tootlp-grpc orotlp-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"
  }
}

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:

[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

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

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

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.

input_tokens excludes cache reads and cache writes. Its own docs say so, and they point out that the GenAI conventions expect input to output_tokens already includes thinking.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.promptTokenCount, which "includes the number of tokens in the cached content." Its 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.

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

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.

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.

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.

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.

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.

logPrompts to false. codex.agents_md.edit``TokenUsage and codex-rs/otel (session telemetry); gemini-cli packages/core/src/telemetry/types.ts at v0.65.0-preview.0 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.

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-codex-an…] indexed:0 read:8min 2026-10-29 · —