Claude Code、Codex 與 Gemini CLI 共用 OpenTelemetry 傳輸,成本資料仍須正規化
Claude Code, Codex and Gemini CLI Just Started Speaking the Same Cost Language
Claude Code、Codex 與 Gemini CLI 在 10 月 13 日至 15 日間各自更新 OpenTelemetry 支援,讓三款工具的用量資料可送往同一收集器。
All three now export OpenTelemetry. One local collector and a short script turn it into a single cost ledger, once you fix three fields.

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

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.otlpHeadersin settings, orGEMINI_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_tokensandcache_creation_1h_tokensto theapi_requestevent, "splitting cache writes by lifetime." It also added GenAI semantic-convention attributes to trace spans, such asgen_ai.provider.nameandgen_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_patchedits 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.

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
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"
}
}
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
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.
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.
-
Claude Code's
input_tokensexcludes cache reads and cache writes. Its own docs say so, and they point out that the GenAI conventions expect input to include them.output_tokensalready includes thinking. -
Codex reports
input_token_countwith 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_countincludes reasoning. -
Gemini CLI sets
input_token_countfrom the Gemini API'spromptTokenCount, which "includes the number of tokens in the cached content." Itsoutput_token_countdoesn't include thoughts; those are a separatethoughts_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
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.

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
- Run one collector on localhost with a file exporter. Validate the config before you trust it.
- Turn on each tool with the snippets above, in user-level config.
-
Set Gemini's
logPromptstofalse. Keep the collector's redact step anyway. - Normalise before you add. Uncached input, cache read, cache write, and output including reasoning.
- Price what isn't priced with your own rate table. Never let a script guess.
- Watch the 1-hour column. If it's large and your sessions are short, you're paying for a lifetime you don't use.
-
Watch
codex.agents_md.editif Codex works in repos with an AGENTS.md. - 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
- Release notes: Claude Code v2.1.300 (14 Oct 2026); Codex rust-v0.163.0 (15 Oct), PRs #52278 and #51652; Gemini CLI v0.65.0-preview.0 (13 Oct), PR #29641; Claude Platform release notes (14 Oct)
- Docs: Claude Code monitoring; Codex config reference; Gemini CLI telemetry; Gemini API usage metadata; Anthropic prompt caching (all read 21 Oct 2026)
- Source code: openai/codex
TokenUsageandcodex-rs/otel(session telemetry); gemini-clipackages/core/src/telemetry/types.tsat v0.65.0-preview.0 - OpenTelemetry Collector releases (v0.163.1); Grafana dashboard 25645
- Previous articles: Compact, Clear or Hand Off?, Where Did My Claude Usage Go?, How Much Should You Invest in Your AGENTS.md?, Cut Your Claude Code Bill by Up to 95%
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.
來源:DEV Community · dev.to
