[BUG] OTEL_METRICS_EXPORTER=otlp silently no-ops in 2.1.113 — OTLP exporter packages not bundled

Status Fixed / completed
Reported on v2.1.113
Maintainer reply ✓ Yes — mhegazy
Activity 15 comments · opened Apr 18, 2026 · closed May 30, 2026
💡 Likely answer: A maintainer (mhegazy, contributor) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Setting OTEL_METRICS_EXPORTER=otlp with any OTEL_EXPORTER_OTLP_PROTOCOL (both http/protobuf and http/json tried) produces zero network traffic. No error is surfaced. OTEL_METRICS_EXPORTER=console works correctly in the same process, so the OTel SDK initializes and metrics accumulate — they just never ship via OTLP.

The Bun-compiled binary ships only @opentelemetry/api + the console exporter; the @opentelemetry/exporter-metrics-otlp-* packages aren't bundled, and the dynamic require() for them at runtime fails silently inside a try / catch.

What Should Happen?

Expected behavior
Per https://code.claude.com/docs/en/monitoring-usage, setting:

CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_METRICS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector_url
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer token_value

should cause Claude Code to POST OTLP metric payloads to collector_url/v1/metrics at the configured export interval.

Actual behavior
Collector receives zero requests from the client.
lsof -p <claude-pid> -i -n during a session shows no connections to the collector IP.
No error is printed to stderr, no log file appears under ~/.claude/, OTEL_LOG_LEVEL=debug produces no output.
Swapping otlp → console in the same shell, same env, works perfectly and dumps full metrics to stdout.
Verified on the collector end: a synthetic curl -X POST https://collector_url/v1/metrics with the same bearer token returns HTTP 200 — so the collector side is healthy.

Error Messages/Logs

Steps to Reproduce

  1. Stand up any OTLP/HTTP receiver that prints on receipt. Any of:

otel/opentelemetry-collector-contrib with an otlp receiver and debug exporter,
a tiny Go/Node OTLP receiver stub,
or just ncat -lk 4318 and check it never receives a byte.

  1. Run Claude Code with OTLP configured:

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_METRIC_EXPORT_INTERVAL=1000 \
claude -p "say hi"

  1. Swap the exporter to console — confirm OTel IS initializing:

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=console \
OTEL_METRIC_EXPORT_INTERVAL=1000 \
claude -p "say hi"
Prints full claude_code.session.count / claude_code.cost.usage /
claude_code.token.usage data-point objects to stdout.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.113

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Root cause (hypothesis, with evidence)
The Bun-compiled binary does not include the OTLP exporter packages. The SDK's factory code dynamically require()s them at runtime, resolves to null/undefined, and a destructuring assignment throws — which is caught and swallowed.

Evidence — strings in the binary:

$ strings /Users/<me>/.local/share/claude/versions/2.1.113 \
| grep -oE '@opentelemetry/[a-z0-9/._-]+' | sort -u
@opentelemetry/api
Only the API package. No exporter packages.

Error strings baked into the binary (clearly caught, never surfaced):

Cannot destructure property 'OTLPMetricExporter' from null or undefined value
Cannot destructure property 'OTLPLogExporter' from null or undefined value
Cannot destructure property 'OTLPTraceExporter' from null or undefined value
Console exporter IS bundled (which is why it works):

$ strings /Users/<me>/.local/share/claude/versions/2.1.113 \
| grep -iE 'consolemetricexporter'
ConsoleMetricExporter
Installing the missing packages globally (npm i -g @opentelemetry/exporter-metrics-otlp-proto) and setting NODE_PATH=$(npm root -g) does not help — Bun-compiled binaries don't resolve externals at runtime the way a Node script would.

Minimal repro

  1. Stand up any OTLP/HTTP receiver that prints on receipt. Any of:

otel/opentelemetry-collector-contrib with an otlp receiver and debug exporter,
a tiny Go/Node OTLP receiver stub,
or just ncat -lk 4318 and check it never receives a byte.

  1. Run Claude Code with OTLP configured:

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_METRIC_EXPORT_INTERVAL=1000 \
claude -p "say hi"

  1. Swap the exporter to console — confirm OTel IS initializing:

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=console \
OTEL_METRIC_EXPORT_INTERVAL=1000 \
claude -p "say hi"
Prints full claude_code.session.count / claude_code.cost.usage /
claude_code.token.usage data-point objects to stdout.

Impact
This breaks the entire "run your own OpenTelemetry collector" story documented at https://code.claude.com/docs/en/monitoring-usage. Orgs deploying Claude Code + a collector for cost/productivity observability currently have no way to get metrics in without downgrading (if an earlier bundled version existed) or falling back to the Admin-API pull.

Workaround
None on the client side. Orgs using Claude Code telemetry have to either:

Fall back to the Admin API's Claude Code usage endpoint (daily granularity only, no live view), or
Scrape console exporter output from stdout and re-emit it out-of-band (brittle).
Suggested fix
Bundle at least @opentelemetry/exporter-metrics-otlp-proto, @opentelemetry/exporter-metrics-otlp-http, and @opentelemetry/exporter-metrics-otlp-grpc into the compiled binary. If bundle size is a concern, gate on OTEL_METRICS_EXPORTER detection and fail loud instead of silent when the selected exporter isn't available — current silent failure is the worst outcome.

View original on GitHub ↗

14 Comments

github-actions[bot] · 4 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/32699

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

0xbrainkid · 4 months ago

OTEL_METRICS_EXPORTER=otlp silently no-oping in 2.1.113 — because the OTLP exporter packages are not bundled in the native binary — is the same class of regression as the HTTP MCP timeout config (#50289) and the plugin cache wipe (#48985): the native binary transition from 2.1.112 to 2.1.113 changed what Node.js packages are included in the bundle, and the OTLP exporter was not brought along.

This is a significant observability regression for any organization that relies on OTEL metrics for production monitoring of Claude Code usage. The metrics export appears to work (no error) but produces no data — exactly the kind of silent failure that is hardest to catch in production.

The two-part fix:

  1. Bundle the missing OTLP exporter packages. The native binary needs @opentelemetry/exporter-metrics-otlp-grpc (for gRPC/protobuf) or @opentelemetry/exporter-metrics-otlp-http (for JSON over HTTP) to be included. These are not in the current bundle.
  1. Fail loudly if a configured exporter cannot be loaded. If OTEL_METRICS_EXPORTER=otlp is set but the OTLP package is not available, Claude Code should log an error: "OTLP metrics exporter configured but package not available — metrics will not be exported." Silent no-op is unacceptable for a monitoring configuration.

Workaround while the fix is deployed: If you can use OTEL_METRICS_EXPORTER=console instead, metrics will be logged to stdout/stderr. Less useful for production but confirms OTEL is configured correctly.

Related: #49893 (add OTEL effort tracking) and #50416 (expose full tool call details) — the overall OTEL observability for Claude Code is a work in progress, and this regression is a significant setback for it.

ten-jampa · 4 months ago

Confirming this on 2.1.111 and inside the Cowork VM — this is also the root cause of #39471

Re-ran @JuanNephrota's exact strings methodology against an older Cowork-bundled binary and against a live Cowork VM session. Two data points worth adding to this issue:

  1. The regression is older than 2.1.113. @0xbrainkid suggested the bundling gap was introduced by the 2.1.112 → 2.1.113 native-binary transition. The same pattern is present in 2.1.111 — so whatever the cutover was, it happened earlier (or the OTLP transport packages were never bundled in the native binary at all).
  2. This is the root cause of #39471 (Cowork OTLP monitoring not emitting events). Multiple orgs in that thread (Datadog, ADOT/X-Ray, Azure App Insights, Sumo Logic, custom collectors) report the exact same "config accepted, zero requests fire, no error surface" pattern. Cowork has no alternative observability surface (Audit Logs, Compliance API, and Data Exports do not cover Cowork sessions per the docs), so this bug is the blocker for every org trying to monitor Cowork.

---

Binary under test

  • Path: ~/Library/Application Support/Claude/claude-code-vm/2.1.111/claude
  • file: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, for GNU/Linux 3.7.0, BuildID[sha1]=7184009e379dd1270157ebc863f106f3bdd1a40e, not stripped
  • SHA256: 99376866bf7ec367142d3be548c17184a79f30a97318441ee9a00f78e51246e7
  • Size: 236,128,832 bytes
  • Baked version string in binary: VERSION:"2.1.111", ... PACKAGE_URL:"@anthropic-ai/claude-code"
  • Host: Claude Desktop 1.3883.0, macOS Apple Silicon (the Cowork SDK version is pinned by the Desktop app release; "2.1.111" is baked directly into app.asar)

Strings evidence on 2.1.111 (identical pattern to 2.1.113)

$ strings -a <bin> | grep -oE '@opentelemetry/[a-z0-9._/-]+' | sort -u
@opentelemetry/api

$ strings -a <bin> | grep -oE 'exporter-[a-z-]+-otlp-[a-z-]+' | sort -u
(no output)

$ strings -a <bin> | grep "Cannot destructure property 'OTL"
Cannot destructure property 'OTLPMetricExporter' from null or undefined value
Cannot destructure property 'OTLPLogExporter' from null or undefined value
Cannot destructure property 'OTLPTraceExporter' from null or undefined value

$ strings -a <bin> | grep -oE 'OTLP[A-Za-z]+(Exporter|Delegate|Transport|Config)' | sort -u
OTLPExportDelegate
OTLPExporterConfig
OTLPLogExporter
OTLPMetricExporter
OTLPTraceExporter

$ strings -a <bin> | grep -oE '[A-Za-z]+MetricExporter|[A-Za-z]+LogExporter' | sort -u
ConsoleMetricExporter
InMemoryMetricExporter
OTLPLogExporter
OTLPMetricExporter
getOtlpLogExporter

$ strings -a <bin> | grep -E '^claude_code\.' | sort -u
claude_code.active_time.total
claude_code.code_edit_tool.decision
claude_code.commit.count
claude_code.cost.usage
claude_code.hook
claude_code.interaction
claude_code.lines_of_code.count
claude_code.llm_request
claude_code.pull_request.count
claude_code.session.count
claude_code.token.usage
claude_code.tool
claude_code.tool.blocked_on_user
claude_code.tool.execution

The full OTLP protobuf schema (opentelemetry.proto.{trace,metrics,logs,common,resource}.v1.*) is also compiled in, and the dynamic-require code fragment matching your root-cause hypothesis is observable in the binary text:

else if (O === "otlp") {
  let z = process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?.trim()
       || process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim(),
      Y = uU8();
  switch (z) {
    case "grpc": {
      let { OTLPMetricExporter: H } = await Promise.resolve().then(() => I(Mc7(), 1));
      K.push(new H);
      break;
    }
    case "ht...

Mc7() resolves to null → destructure throws → surrounding try/catch swallows → zero requests fire. Exactly your root-cause hypothesis.

Cowork VM runtime confirmation (new signal)

Ran the console-exporter test inside an active Cowork session (host: Claude Desktop 1.3883.0, macOS Apple Silicon; VM env: CLAUDE_CODE_IS_COWORK=1, CLAUDE_CODE_ENTRYPOINT=local-agent):

CLAUDE_CODE_ENABLE_TELEMETRY=1 \
OTEL_METRICS_EXPORTER=console \
OTEL_METRIC_EXPORT_INTERVAL=2000 \
  claude --print "hello"

Emitted a full claude_code.session.count data point to stdout within ~89 ms with full attributes (user.id hashed, user.email, session.id UUID, terminal.type=non-interactive). So the OTel SDK initializes fine inside the Cowork VM — the failure is purely at the OTLP exporter boundary, matching the 2.1.113 behavior.

Also verified that the Admin UI → Cowork → Monitoring configuration (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_LOG_USER_PROMPTS=1, protocol, etc.) does propagate into the VM claude subprocess environment. That rules out env-var propagation bugs and narrows this issue exclusively to the exporter packaging.

Impact this adds to #50567

  • Cowork has no working OTLP observability today. The Admin UI surfaces Admin → Cowork → Monitoring as a first-class feature and sets expectations that it works; in practice every OTLP backend silently receives zero events. This is the root cause of #39471, where the same reproduction is reported independently against Datadog, ADOT/X-Ray, Azure Application Insights, Sumo Logic, and custom collectors.
  • Cowork users cannot self-upgrade to a newer client-side CLI. The VM SDK version ("2.1.111") is baked directly into the Claude Desktop app.asar manifest and installed into ~/Library/Application Support/Claude/claude-code-vm/<version>/claude at session start. It does not track the npm CLI release channel. Any fix needs to land in a Desktop release that bumps the pinned VM SDK to a version with the OTLP exporter packages actually bundled.
  • Cowork defaults OTEL_LOG_USER_PROMPTS=1. The logs signal (not just metrics) matters as much as metrics here, and OTLPLogExporter has the same destructure error baked in. A fix needs to cover all three signals.

Minor clarification for the bundle check

PrometheusExporter is bundled in the 2.1.111 binary (appeared in strings alongside ConsoleMetricExporter and InMemoryMetricExporter). For standalone-CLI users, OTEL_METRICS_EXPORTER=prometheus + a local scrape + a Prometheus-remote-write-to-OTLP bridge is a viable workaround today. Not usable for Cowork since there is no way to reach an in-VM Prometheus endpoint from the host through the egress proxy.

+1 to the suggested fix

Bundle @opentelemetry/exporter-{metrics,logs,traces}-otlp-{proto,http} and convert the silent swallow into a loud startup error ("OTLP <signal> exporter configured but package not available — telemetry will not be exported"). Silent no-op on a monitoring configuration is the worst possible failure mode.

Suggested labels to add

  • area:cowork (this manifests as a Cowork blocker in addition to the standalone CLI)
  • regression (confirmed present on at least 2.1.111 through 2.1.113)
AZVGBND · 4 months ago

+1, confirming this Cowork blocker persists on Claude Desktop
1.4758.0 (build fb266c, 2026-04-24), macOS Apple Silicon. Same
behavior as ten-jampa documented on 1.3883.0: settings persist
correctly, env vars propagate (verified via OTEL_METRICS_EXPORTER=
console-style alternative testing), but OTLP exporter never sends.
This issue blocks Cowork OTel observability for our org regardless
of which OTLP backend or endpoint we point at.

m13v · 4 months ago

i lost two afternoons last week to this exact silent failure. had OTEL_METRICS_EXPORTER=otlp set, collector logs were empty, no errors anywhere, kept second-guessing my collector config before stracing the bun binary and seeing the require() for exporter-metrics-otlp-http getting swallowed in a try/catch. switched to OTEL_METRICS_EXPORTER=console just to confirm metrics were actually accumulating, they were. ended up giving up on otel for usage tracking and pulling the rolling 5-hour and weekly numbers straight off the claude.ai settings/usage endpoint instead, which is what anthropic actually enforces against. the silent fallback is the worst part of this bug, a thrown error on missing exporter would've saved me half a day.

tavori · 4 months ago

same problem in 2.1.123 version

mhegazy contributor · 4 months ago

I am not able to reproduce this locally. I can not deduce from the report alone what is going on. Can you please share the specific error you are getting: run again with --debug-file /tmp/otel.log and share the lines matching grep -i "3P telemetry" /tmp/otel.log. this should give me the exact error where it is failing.

andersenleo · 3 months ago

Adding org-wide evidence from a different Datadog tenant that confirms this regression and helps narrow the package-bundling hypothesis.

Setup

  • Backend: Datadog EU OTLP intake (https://otlp.datadoghq.eu/v1/metrics)
  • Plain http/protobuf, no mTLS
  • ~26 distinct users in our tenant, mix of Claude Code versions

Last 24h claude_code.session.count grouped by version resource attribute

| version | sessions emitted (24h) |
|----------|-----------------------:|
| 2.1.118 | 2 |
| 2.1.119 | 0 |
| 2.1.120 | 0 |
| 2.1.121 | 0 |
| 2.1.122 | 0 |
| 2.1.123 | 0 |
| 2.1.126 | 0 |

So in our tenant, plain HTTP/protobuf (no mTLS) does still work on 2.1.118 but is silent on every 2.1.119+ build observed (including 2.1.126). #52353 reports plain-config also broken on 2.1.118 with mTLS — our data is the dual: 2.1.118 still works for the non-mTLS code path. That's consistent with a packaging/bundling issue where some OTLP exporter modules survive the bundling change and others don't, and the breakage widens further at 2.1.119.

Debug log on 2.1.126 (anonymized)

claude --debug produces these [3P telemetry] lines:

[3P telemetry] Waiting for remote managed settings before telemetry init
[3P telemetry] Remote managed settings loaded, initializing telemetry
[3P telemetry] isTelemetryEnabled=true (CLAUDE_CODE_ENABLE_TELEMETRY=1)
[3P telemetry] getOtlpLogExporters: types=[], protocol=http/protobuf, endpoint=undefined
[3P telemetry] Created 0 log exporter(s)

No getOtlpMetricExporters line, no Created N metric exporter(s) line — i.e. the metric-exporter init branch never executes (or throws inside the silent try/catch and is swallowed). This matches the bundled-package hypothesis in the OP: the metric OTLP exporter dynamic require fails, the catch swallows, and that branch silently no-ops.

Other diagnostics

  • DD-API-KEY validated independently (curl … /v1/metrics --data-binary ''400 Payload is empty) — credentials and endpoint healthy.
  • All OTEL env vars are present in Claude's process environment (printenv | grep OTEL), so this isn't an env-var availability issue.
  • No local managed-settings.json; remote managed settings load successfully before telemetry init.

Asks

  • Add a fail-loud path: when the OTLP exporter package(s) can't be loaded, emit at least one WARN-level [3P telemetry] line (not silently swallowed in try/catch). Right now this is debuggable only by side channels (network captures or watching backend ingestion) which is brutal at fleet scale.
  • Confirm whether the bundling fix shipped in any 2.1.x release; we'd be happy to retest.
  • Workaround we landed for now: pin to 2.1.112 / 2.1.118 depending on whether mTLS is in use.

(Filed our duplicate as #56021; closing that and pointing here.)

DanielKatz67 · 3 months ago

Hi — any update on this? Still reproducing on v2.1.143.
Could someone from Anthropic confirm whether a fix is being tracked? Thanks!
@JuanNephrota @mhegazy @andersenleo @ten-jampa @AZVGBND

DanielKatz67 · 3 months ago

Update from our side:
Confirmed working on v2.1.143 with mTLS after switching
from OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE / _CLIENT_KEY
to CLAUDE_CODE_CLIENT_CERT / CLAUDE_CODE_CLIENT_KEY
(the correct vars for http/protobuf per the updated mTLS docs).

andersenleo · 3 months ago

Still not working for us - sending OTEL to Datadog :/

andersenleo · 3 months ago

Ping! Anyone with an update, or successfully working metrics in Claude Code?

andersenleo · 3 months ago

Alright - I can verify metrics works for us now. And might have been working all along.
We accidentally used a stale Datadog API Key 🙈. So - this works for us now.

mxw-sec · 2 months ago

Can any one confirm this actually is working, we are running into the same issue on the latest version of Claude Code, console export Works, so we know that is valid, Manual calls using claude work, so we know thats not the issue, but automatic OTLP exports do Nothing

Showing cached comments. Read the full discussion on GitHub ↗