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. 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 https://www.reddit.com/r/ClaudeCode/comments/1xbc9rt/ 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 https://github.com/open-telemetry/opentelemetry-collector-releases/releases/tag/v0.163.1 : 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 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 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" } } Put this in ~/.claude/settings.json , or export the same variables in your shell. Claude Code ignores these variables https://code.claude.com/docs/en/monitoring-usage 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 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 https://developers.openai.com/codex/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 https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/telemetry.md , 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 https://grafana.com/grafana/dashboards/25645 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 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? https://medium.com/p/21c26def6bae , 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 https://platform.claude.com/docs/en/build-with-claude/prompt-caching 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? https://medium.com/p/95d6ab17b8b6 and Cut Your Claude Code Bill by Up to 95% https://medium.com/techtrends-digest/cut-your-claude-code-bill-by-up-to-95-3cba02c11cfc : 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? https://medium.com/p/69f8e36583bc 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 https://r3dtuxedo.substack.com/subscribe 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 https://andre-burgstahler-9n5.pages.dev/ . This article was written with AI assistance and reviewed by the author.