[BUG] Desktop app OTel exporter sends malformed doubleValue ({}) for a numeric attribute on some api_request events

Status Open
Reported on v2.1.208
Maintainer reply None cached
Activity 1 comment · opened Jul 29, 2026

What's Wrong?

The Claude Desktop app's built-in OpenTelemetry exporter (configured via Settings → Telemetry & updates → OpenTelemetry collector endpoint) intermittently sends a malformed OTLP log record where a numeric attribute — almost certainly cost_usd on an api_request/similar event — is serialized as {"doubleValue": {}} (an empty JSON object) instead of {"doubleValue": <number>}.

This is invalid per the OTLP JSON encoding (AnyValue.double_value must be a JSON number), and any spec-compliant OTLP collector rejects the entire log batch containing the malformed record. In our case (a self-hosted OpenObserve instance), this surfaces as a 400 with:

Invalid json: invalid type: map, expected f64 at line 1 column XXXX

Net effect: cost/token-carrying log events never successfully ingest anywhere downstream, while other event types (tool_decision, tool_result, hook_registered, etc.) — which don't hit this code path — ingest fine. Confirmed by inspecting the actual stream schema after a live session: fields like event_name, decision, tool_name, user_email are all present and populated, but cost_usd, input_tokens, output_tokens, and model never appear at all, despite api_request events clearly being sent (repeated 400s on /v1/logs from the same client).

What Should Happen?

Numeric attributes (cost, token counts) should always serialize as valid OTLP doubleValue/intValue numbers. If the underlying value is ever undefined/NaN/unavailable at serialization time, the exporter should omit the attribute entirely (or send 0), not emit {} in its place — never produce a structurally invalid AnyValue.

Error Messages/Logs

2026-07-29T18:25:19.653351512+00:00 ERROR openobserve::handler::http::request::logs::ingest: [LOGS:OTLP] Invalid json: org_id: default invalid type: map, expected f64 at line 1 column 4247
2026-07-29T18:25:19.653659800+00:00 INFO config::axum::middlewares::access_log: 192.168.0.188 "POST /api/default/v1/logs HTTP/1.1" 400 - "8634" "-" "OTel-OTLP-Exporter-JavaScript/0.208.0" 0.000

Steps to Reproduce

I could not capture the exact live payload (it fails during initial JSON deserialization on the collector side, before any application-level logging runs, so the collector itself never logs the offending body). But I reproduced the exact same error class synthetically by sending a minimal OTLP JSON logs payload to a real OpenObserve instance with a doubleValue set to an empty object instead of a number:

\\\bash
curl -X POST "http://<otel-collector>/v1/logs" \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "claude-code-desktop"}}]},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "<current-time-ns>",
"body": {"stringValue": "claude_code.api_request"},
"attributes": [
{"key": "event_name", "value": {"stringValue": "api_request"}},
{"key": "cost_usd", "value": {"doubleValue": {}}},
{"key": "model", "value": {"stringValue": "claude-sonnet-5"}}
]
}]
}]
}]
}'
\
\\

This reproduces byte-for-byte the same error class (\invalid type: map, expected f64\) seen from the real Desktop app traffic, strongly suggesting the app is producing exactly this malformed shape for some fraction of real \api_request\ events.

To observe the real-world symptom end-to-end:

  1. Configure Claude Desktop's OpenTelemetry settings to point at any spec-compliant OTLP HTTP/JSON or HTTP/protobuf collector (self-hosted OpenObserve in our case), with a valid Authorization header.
  2. Use Claude Code / Cowork normally for a while (enough to generate several real API requests with cost/token usage).
  3. Inspect the collector's logs for /v1/logs ingestion — some requests return 400 with the error above; the rest (tool/hook events without cost data) succeed.
  4. Query the resulting stream's schema — cost_usd, input_tokens, output_tokens, model are entirely absent, even though other event fields are present and populated.

Note: this is orthogonal to stream-name header routing or content-capture settings — I tested disabling tool-content capture and it did not change this behavior; the malformed field appears independent of content-capture configuration.

Claude Code Version

2.1.208 (Claude Code) — CLI. The bug is specifically in the Desktop/Cowork app's OTel exporter, service_version \1.24012.9\, instrumentation library \com.anthropic.claude_code.events\, exporter user-agent \OTel-OTLP-Exporter-JavaScript/0.208.0\.

Platform

Anthropic API (interactive/subscription auth, not API key)

Operating System

Windows (10.0.26200)

Terminal/Shell

Other (Claude Desktop app's own Telemetry & updates settings panel — not a terminal-based configuration)

Is this a regression?

I don't know — this is the first time telemetry export was configured against a real OTLP collector on this machine, so there's no prior-version baseline to compare against.

Additional Information

  • Collector used for testing: OpenObserve (self-hosted, openobserve/openobserve:latest), OTLP over http/json.
  • Confirmed this is a protocol-level malformed payload, not an application-schema/ingestion-config issue, by reproducing the identical error text with a minimal hand-crafted OTLP payload against the same collector.
  • Happy to provide more detail (collector version, full request/response headers) if useful — I don't have access to the exact raw bytes of the real failing request since the collector fails during initial deserialization, before it logs anything.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗