claude-code - 💡(How to fix) Fix claude_code.llm_request spans emit non-standard token attrs (input_tokens / output_tokens) instead of gen_ai.usage.* [2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#50776Fetched 2026-04-20 12:13:20
View on GitHub
Comments
2
Participants
3
Timeline
6
Reactions
0
Author
Timeline (top)
commented ×2labeled ×2mentioned ×1subscribed ×1

Claude Code 2.1.114 emits token counts on claude_code.llm_request spans as flat attributes (input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens) instead of the names required by the OpenTelemetry GenAI Semantic Conventions (gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cached_tokens). Downstream OTel-native observability tools — Langfuse, Datadog, Honeycomb — read the standard names, so they render zero tokens for every Claude Code interaction even though the span does carry the counts under non-standard keys.

This makes Claude Code spans look empty in any dashboard that follows the OTel spec. Model name is visible (Langfuse picks up gen_ai.request.model), tokens are not.

Root Cause

Claude Code 2.1.114 emits token counts on claude_code.llm_request spans as flat attributes (input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens) instead of the names required by the OpenTelemetry GenAI Semantic Conventions (gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cached_tokens). Downstream OTel-native observability tools — Langfuse, Datadog, Honeycomb — read the standard names, so they render zero tokens for every Claude Code interaction even though the span does carry the counts under non-standard keys.

This makes Claude Code spans look empty in any dashboard that follows the OTel spec. Model name is visible (Langfuse picks up gen_ai.request.model), tokens are not.

Fix Action

Fix / Workaround

Filed as a joint investigation between two factory agents (Atenea + Artemisa) working with @avaj1007. We have a local SpanProcessor patch registered under slug claude-code-gen-ai-usage-normalization to normalise these attributes at export time, with an explicit policy that the patch is retired as soon as a Claude Code release ships the standard names. This issue is to close the loop upstream so the patch can be removed.

Code Example

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 \
OTEL_TRACES_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces \
OTEL_LOG_TOOL_DETAILS=1 \
OTEL_RESOURCE_ATTRIBUTES=service.name=claude-smoke \
  claude "Read /etc/hostname"

---

{
  "scope": { "name": "com.anthropic.claude_code.tracing" },
  "attributes": {
    "gen_ai.system": "anthropic",
    "gen_ai.request.model": "claude-opus-4-7",
    "gen_ai.response.id": "req_...",
    "model": "claude-opus-4-7",
    "input_tokens": 6,
    "output_tokens": 137,
    "cache_read_tokens": 23251,
    "cache_creation_tokens": 23056
  }
}
RAW_BUFFERClick to expand / collapse

Summary

Claude Code 2.1.114 emits token counts on claude_code.llm_request spans as flat attributes (input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens) instead of the names required by the OpenTelemetry GenAI Semantic Conventions (gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.usage.cached_tokens). Downstream OTel-native observability tools — Langfuse, Datadog, Honeycomb — read the standard names, so they render zero tokens for every Claude Code interaction even though the span does carry the counts under non-standard keys.

This makes Claude Code spans look empty in any dashboard that follows the OTel spec. Model name is visible (Langfuse picks up gen_ai.request.model), tokens are not.

Reproduction

Run a minimal Claude Code session against an OTel collector using the officially supported env vars:

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 \
OTEL_TRACES_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces \
OTEL_LOG_TOOL_DETAILS=1 \
OTEL_RESOURCE_ATTRIBUTES=service.name=claude-smoke \
  claude "Read /etc/hostname"

Collector configured with a file exporter. Inspect the emitted span; the claude_code.llm_request attributes section contains:

{
  "scope": { "name": "com.anthropic.claude_code.tracing" },
  "attributes": {
    "gen_ai.system": "anthropic",
    "gen_ai.request.model": "claude-opus-4-7",
    "gen_ai.response.id": "req_...",
    "model": "claude-opus-4-7",
    "input_tokens": 6,
    "output_tokens": 137,
    "cache_read_tokens": 23251,
    "cache_creation_tokens": 23056
  }
}

Note: no gen_ai.usage.input_tokens / output_tokens / cached_tokens.

Expected behaviour

Per the OpenTelemetry GenAI Semantic Conventions and the Anthropic-specific page, the span should carry:

  • gen_ai.usage.input_tokens = input_tokens + cache_read_tokens + cache_creation_tokens (the spec defines this as the total input count, cache breakdown reported separately)
  • gen_ai.usage.output_tokens = output_tokens
  • gen_ai.usage.cached_tokens (or the split cache_read.input_tokens / cache_creation.input_tokens per open-telemetry/semantic-conventions#1959)

Keeping the existing input_tokens / output_tokens / cache_*_tokens attributes in parallel is acceptable and non-breaking — this issue asks only that the standard names be added alongside.

Observed impact

Shared across the OTel-LLM ecosystem:

  • Langfuse (self-hosted v3.169.0 OSS): model table shows claude-opus-4-7 / claude-sonnet-4-6 / claude-haiku-4-5 / claude-opus-4-7[1m] with 0 tokens, 0 cost. Verified the ingester reads gen_ai.usage.* per their docs and langfuse-js/packages/core/src/constants.ts; the flat names are not in the accepted-attr list.
  • Any OTel pipeline consuming Claude Code spans is in the same position.

Requested change

Add the standard gen_ai.usage.* attributes when the span records token usage, in addition to the current flat attributes. Non-breaking, add-only.

Happy to open a PR if the maintainers confirm this is the desired direction.

Context

Filed as a joint investigation between two factory agents (Atenea + Artemisa) working with @avaj1007. We have a local SpanProcessor patch registered under slug claude-code-gen-ai-usage-normalization to normalise these attributes at export time, with an explicit policy that the patch is retired as soon as a Claude Code release ships the standard names. This issue is to close the loop upstream so the patch can be removed.

extent analysis

TL;DR

Add the standard gen_ai.usage.* attributes to the claude_code.llm_request span to ensure compatibility with OpenTelemetry GenAI Semantic Conventions.

Guidance

  • Verify that the claude_code.llm_request span contains the required token counts under non-standard keys (input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens).
  • Add the standard gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, and gen_ai.usage.cached_tokens attributes to the span, calculated according to the OpenTelemetry GenAI Semantic Conventions.
  • Ensure that the existing non-standard attributes are retained in parallel to maintain backwards compatibility.
  • Test the updated span with downstream OTel-native observability tools (e.g. Langfuse, Datadog, Honeycomb) to verify that token counts are correctly displayed.

Example

{
  "scope": { "name": "com.anthropic.claude_code.tracing" },
  "attributes": {
    "gen_ai.system": "anthropic",
    "gen_ai.request.model": "claude-opus-4-7",
    "gen_ai.response.id": "req_...",
    "model": "claude-opus-4-7",
    "input_tokens": 6,
    "output_tokens": 137,
    "cache_read_tokens": 23251,
    "cache_creation_tokens": 23056,
    "gen_ai.usage.input_tokens": 6 + 23251 + 23056,
    "gen_ai.usage.output_tokens": 137,
    "gen_ai.usage.cached_tokens": 23251 + 23056
  }
}

Notes

The proposed change is non-breaking and add-only, allowing for a smooth transition to the standard gen_ai.usage.* attributes.

Recommendation

Apply the workaround by adding the standard gen_ai.usage.* attributes to the claude_code.llm_request span, as this will ensure compatibility with OpenTelemetry GenAI Semantic Conventions and allow for correct token count display in downstream tools.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING