Skip to content

Fix token double-count from dual Claude Code telemetry channels#155

Merged
shukieshah merged 1 commit into
mainfrom
fix/token-usage-dual-channel-double-count
Jun 12, 2026
Merged

Fix token double-count from dual Claude Code telemetry channels#155
shukieshah merged 1 commit into
mainfrom
fix/token-usage-dual-channel-double-count

Conversation

@shukieshah

@shukieshah shukieshah commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Problem

End-to-end testing of the token usage feature on a live Claude Code session surfaced that every token field is counted twice. Claude Code reports each request's usage on both of its OTel channels, and Beacon's install enables both:

  • Logs — a claude_code.api_request record carrying full gen_ai.usage (no metric_name).
  • Metricsclaude_code.token.usage / claude_code.cost.usage datapoints.

tokens.Aggregate summed every usage-bearing event with no channel de-dup, so totals, by_model, by_session, by_harness, and the dashboard were all ~2× inflated. Captured proof — the two channels were byte-identical:

field log channel metric channel reported (2×)
input 4,261 4,261 8,522
output 14,502 14,502 29,004
cache_read 1,172,980 1,172,980 2,345,960
cache_creation 86,426 86,426 172,852
cost_usd 0 1.096 1.096 ✅

Cost was already correct because it rides only on claude_code.cost.usage. The [1m] two-row symptom was downstream of this (the channels label the model differently: base name on logs, [1m] request model on metrics).

Fix

Treat the log/span channel as the token source of truth. Per (harness, session) scope, zero any usage field that channel reports on the scope's metric-channel events, and drop metric events left with no usage. Fields the log channel never carries (cost) survive on the metric channel, so cost still lands exactly once. Scopes with no log/span channel (metrics-only runtimes like Codex/SDK) are left untouched, so their usage is never dropped.

CLI-only change — the collector binary is unaffected.

Verification

  • go test ./..., go test -race ./internal/endpoint/..., gofmt, go vet all pass.
  • New TestAggregateDedupesDualChannelUsage covers dual-channel collapse, cost-only survival on [1m], and a metrics-only scope passthrough.
  • Against real captured Claude Code telemetry, report totals now equal log-channel tokens + metric-channel cost exactly (e.g. haiku 7940/5032/5597503970/2516/279875), [1m] rows are cost-only, the bogus 103.5% utilization artifact is gone, and the dashboard /api/tokens matches the CLI.

🤖 Generated with Claude Code


Note

Medium Risk
Changes core token aggregation logic used by CLI and dashboard; wrong dedupe rules could under-count metrics-only runtimes or drop valid usage, though scoped to (harness, session) and covered by new tests.

Overview
Fixes ~2× inflated token totals when Claude Code telemetry is ingested from both OTel logs (claude_code.api_request, no metric_name) and metrics (claude_code.token.usage).

tokens.Aggregate now runs dedupeOverlappingChannels after collecting usage events and before cumulative delta resolution. Per (harness, session), log/span events define which usage fields are authoritative; matching fields on metric events are zeroed, empty metric rows are dropped, and fields only on metrics (e.g. cost on claude_code.cost.usage) are kept. Scopes with metrics only are unchanged.

Adds TestAggregateDedupesDualChannelUsage for dual-channel collapse, cost-only [1m] attribution, and metrics-only passthrough. CLI token commands and dashboard /api/tokens pick this up via the shared aggregator.

Reviewed by Cursor Bugbot for commit a8fb55d. Bugbot is set up for automated code reviews on this repo. Configure here.

Claude Code reports each request's usage on both a claude_code.api_request
log record and the claude_code.token.usage metric. Beacon ingested both, so
every token field (input/output/cache read/cache creation) was counted twice
in totals and every rollup; cost was unaffected because it rides only on
claude_code.cost.usage.

Treat the log/span channel as the token source of truth. Per (harness,
session) scope, zero any usage field that channel reports on the scope's
metric-channel events, and drop metric events left with no usage. Fields the
log channel never carries (cost) survive on the metric channel, so cost still
lands exactly once. Scopes with no log/span channel (metrics-only runtimes)
are untouched, so their usage is not dropped.

Verified against real captured Claude Code telemetry: report totals now equal
log-channel tokens plus metric-channel cost exactly, and the dashboard
/api/tokens matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shukieshah shukieshah self-assigned this Jun 12, 2026
@shukieshah shukieshah merged commit 9f0c0d2 into main Jun 12, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant