[BUG] OTEL_METRICS_EXPORTER=otlp silently no-ops in 2.1.113 — OTLP exporter packages not bundled
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
- 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.
- 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"
- 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
- 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.
- 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"
- 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.
This issue has 14 comments on GitHub. Read the full discussion on GitHub ↗