[BUG] OTLP logs export regressed from Content-Length to Transfer-Encoding: chunked in 2.1.191 — breaks Content-Length-requiring OTLP/HTTP endpoints

Status Fixed / completed
Reported on v2.1.190
Maintainer reply None cached
Activity 12 comments · opened Jul 1, 2026 · closed Jul 15, 2026

Summary

Between Claude Code 2.1.190 and 2.1.191, the OTLP/HTTP logs exporter changed from sending Content-Length to Transfer-Encoding: chunked. The bundled exporter version (OTel-OTLP-Exporter-JavaScript/0.208.0) and the emitted events are unchanged — only the HTTP body framing flipped. OTLP ingestion endpoints that require Content-Length now reject every export, silently.

Environment

  • Claude Code 2.1.191 – 2.1.197 (current)
  • CLAUDE_CODE_ENABLE_TELEMETRY=1, OTEL_LOGS_EXPORTER=otlp, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
  • Target: a Content-Length-requiring OTLP/HTTP ingestion endpoint (common for managed/enterprise collectors and cloud ingestion gateways) — e.g. Azure Monitor Data Collection Endpoints.

What breaks

On 2.1.191+ the export request is sent with Transfer-Encoding: chunked and no Content-Length. Endpoints that require Content-Length reject it. Azure Monitor's DCE returns:

HTTP 400 {"code":"MissingContentLengthHeader","message":"Invalid request. Header 'Content-Length' must be specified."}

The identical payload sent with a Content-Length header returns 204.

Regression bisect

Each published binary run against a local capture endpoint:

| Version | Published | Body framing | Strict endpoint |
|---|---|---|---|
| 2.1.185 | 2026-06-20 | Content-Length | ✅ 204 |
| 2.1.190 | 2026-06-24 | Content-Length | ✅ 204 — last good |
| 2.1.191 | 2026-06-24 | chunked | ❌ 400 — first broken |
| 2.1.193 / 2.1.195 | 2026-06-25 / 26 | chunked | ❌ 400 |

All bundle the same OTel-OTLP-Exporter-JavaScript/0.208.0 and emit identical events (api_request, claude_code.*) — so the change is in Claude Code's bundle, not the OpenTelemetry dependency version.

Impact

We ingest Claude Code's OTLP api_request events into Azure Monitor for usage/cost reporting. The 2.1.191 update silently stopped telemetry from landing, and with no client-side error it wasn't obvious where the break was — we chased server-side and network layers before capturing the raw emission and bisecting to the exact version.

Two gaps compounded it: the framing regression (this bug), plus the endpoint's 400 being swallowed with no log — a total outage looks identical to "working." A single warning on a non-2xx OTLP response would make this class of failure obvious.

Reproduction

  1. Configure the OTLP logs exporter (above), pointing OTEL_EXPORTER_OTLP_LOGS_ENDPOINT at a local HTTP server that logs request headers.
  2. Run any command; inspect the captured request.
  3. On 2.1.190: Content-Length: <N>. On 2.1.191+: Transfer-Encoding: chunked, no Content-Length.

Note (pre-empting "chunked is valid HTTP")

Both framings are valid HTTP/1.1. But the OTLP request body is fully buffered before send, so its length is already known — Content-Length is the correct, more interoperable choice: it works with both lenient and strict endpoints, whereas chunked breaks the strict class for no benefit. 2.1.190 did exactly this; 2.1.191 regressed it.

Suggested fix

Restore Content-Length on the OTLP/HTTP logs request (set the header / end(buffer) on the fully-buffered payload instead of streaming it). Optionally, surface non-2xx OTLP export responses in a log line so silent drops are detectable.

View original on GitHub ↗

12 Comments

CompPhy · 1 month ago

Reproduced on 2.1.201 (Linux, native install) against a self-hosted OpenTelemetry Collector behind a Cilium/Envoy ingress — with evidence the 411 is generated client-side before any network I/O.

Setup

Telemetry env delivered via /etc/claude-code/managed-settings.json:

"env": {
  "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
  "OTEL_METRICS_EXPORTER": "otlp",
  "OTEL_LOGS_EXPORTER": "otlp",
  "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
  "OTEL_EXPORTER_OTLP_ENDPOINT": "https://<collector-host>"
}

Endpoint is an Envoy ingress (Cilium) → otel/opentelemetry-collector-contrib OTLP HTTP receiver (4318).

Observed

Every export attempt fails with 411. From claude --debug -p "ok":

[DEBUG] [3P telemetry] getOtlpReaders: types=["otlp"], interval=60000, protocol=http/protobuf, endpoint=https://<collector-host>
[DEBUG] [3P telemetry] getOtlpLogExporters: types=["otlp"], protocol=http/protobuf, endpoint=https://<collector-host>
[ERROR] [3P telemetry] OTEL diag error: {"message":"Length Required","code":"411","name":"OTLPExporterError","data":"length required", ... "sourceURL":"/$bunfs/root/src/entrypoints/cli.js"}
[ERROR] [3P telemetry] OTEL diag error: {"message":"PeriodicExportingMetricReader: metrics export failed (error OTLPExporterError: Length Required)", ...}

Key finding: the 411 occurs before any network I/O

We pointed OTEL_EXPORTER_OTLP_ENDPOINT at a raw localhost socket listener and re-ran with --debug. The CLI reported the identical OTLPExporterError: Length Required (411) while the listener received zero connections. The error appears to be fabricated inside the CLI/Bun HTTP stack — no request is ever transmitted, so this is not an endpoint/proxy compatibility problem.

Consistent with that, the server side is provably tolerant: the same Envoy ingress + collector accept every hand-crafted variant we sent (valid Transfer-Encoding: chunked for JSON and protobuf, gzip + chunked, Content-Length + chunked combined, even a malformed POST with a body but neither Content-Length nor Transfer-Encoding). We could not produce a 411 from the server with any request shape — only the CLI's exporter reports one.

Other data points

  • Not protocol-specific: identical 411 with OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf and http/json.
  • Not compression-related: identical 411 with OTEL_EXPORTER_OTLP_COMPRESSION=none.
  • OTEL_EXPORTER_OTLP_PROTOCOL=grpc fails differently: 14 UNAVAILABLE: No connection established. Last error: h2 is not supported — the Bun runtime has no HTTP/2 client, so the gRPC path is also unusable. On 2.1.191+ every OTLP export path is dead client-side.
  • The npm distribution is not a workaround: npx @anthropic-ai/claude-code@2.1.201 produces the identical error with the same /$bunfs/ stack — it ships the same Bun-compiled binary.
  • Errors only surface with --debug; otherwise the failure is completely silent.
  • The "pin to 2.1.190" workaround is unavailable to Claude apps gateway users: gateway login requires ≥ 2.1.195, so all gateway deployments currently have no working client telemetry at all.

Happy to provide raw captures or run candidate builds against this setup.

samuliohman · 1 month ago

Reproduced on 2.1.202 (Windows, Claude apps gateway setup). We are evaluating the claude gateway for company-wide adoption, which requires ≥2.1.195, so reverting to 2.1.190 is not an option. Same debug log pattern as above. Please prioritize.

CompPhy · 1 month ago

Here's output from --debug log:

2026-07-07T17:14:06.775Z [DEBUG] [3P telemetry] Waiting for remote managed settings before telemetry init
2026-07-07T17:14:06.775Z [DEBUG] [3P telemetry] Remote managed settings loaded, initializing telemetry
2026-07-07T17:14:06.822Z [DEBUG] [3P telemetry] isTelemetryEnabled=true (CLAUDE_CODE_ENABLE_TELEMETRY=1)
2026-07-07T17:14:06.822Z [DEBUG] [3P telemetry] getOtlpReaders: types=["otlp"], interval=60000, protocol=http/protobuf, endpoint=<ENDPOINT URL>
2026-07-07T17:14:06.845Z [DEBUG] [3P telemetry] getOtlpLogExporters: types=["otlp"], protocol=http/protobuf, endpoint=<ENDPOINT URL>
2026-07-07T17:14:06.846Z [DEBUG] [3P telemetry] Created 1 log exporter(s)
2026-07-07T17:14:06.849Z [DEBUG] [3P telemetry] Event logger set successfully
2026-07-07T17:14:12.238Z [ERROR] [3P telemetry] OTEL diag error: {"message":"Length Required","code":"411","name":"OTLPExporterError","data":"length required","originalLine":"3742","originalColumn":"261374","line":"3742","column":"261374","sourceURL":"/$bunfs/root/src/entrypoints/cli.js","stack":"OTLPExporterError: Length Required\n    at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:3742:261374)\n    at emit (node:events:92:22)\n    at endReadableNT (internal:streams/readable:897:50)\n    at processTicksAndRejections (native:7:39)"}
2026-07-07T17:14:17.645Z [ERROR] [3P telemetry] OTEL diag error: {"message":"Length Required","code":"411","name":"OTLPExporterError","data":"length required","originalLine":"3742","originalColumn":"261374","line":"3742","column":"261374","sourceURL":"/$bunfs/root/src/entrypoints/cli.js","stack":"OTLPExporterError: Length Required\n    at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:3742:261374)\n    at emit (node:events:92:22)\n    at endReadableNT (internal:streams/readable:897:50)\n    at processTicksAndRejections (native:7:39)"}
2026-07-07T17:14:17.798Z [ERROR] [3P telemetry] OTEL diag error: {"message":"PeriodicExportingMetricReader: metrics export failed (error OTLPExporterError: Length Required)","originalLine":"3729","originalColumn":"48749","line":"3729","column":"48749","sourceURL":"/$bunfs/root/src/entrypoints/cli.js","stack":"Error: PeriodicExportingMetricReader: metrics export failed (error OTLPExporterError: Length Required)\n    at _doRun (/$bunfs/root/src/entrypoints/cli.js:3729:48749)\n    at processTicksAndRejections (native:7:39)","name":"Error"}
CompPhy · 1 month ago

Still broken as of the 2.1.206 release as well.

CompPhy · 1 month ago

Lots of "fixes" to 3P related things in 2.1.207, but this is still the same error.

DamianKomorowski · 1 month ago

In version 2.1.207 telemetry from client to gateway does not work.

velimattiv · 1 month ago

Still broken in 2.1.210.

Re-verified empirically today: pointed the http/protobuf logs exporter (OTEL_LOGS_EXPORTER=otlp, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf) at a local header-capture server and ran a claude -p session. Every export still arrives as:

POST /v1/logs HTTP/1.1
content-type: application/x-protobuf
user-agent: OTel-OTLP-Exporter-JavaScript/0.208.0
transfer-encoding: chunked
(no Content-Length)

Bundled exporter version unchanged (0.208.0), so this remains the body-framing flip introduced in 2.1.191 — Content-Length-requiring OTLP endpoints (e.g. Azure Monitor DCE, which responds 400 MissingContentLengthHeader) still reject every export, silently.

velimattiv · 1 month ago

This was closed on 2026-07-15 without a linked fix or comment, but the regression is still present in the latest release, 2.1.211 (current npm latest, and its changelog doesn't mention this issue).

Re-verified empirically just now, same method as my 2.1.210 report: http/protobuf logs exporter pointed at a local header-capture server, three exports captured — all three arrive with transfer-encoding: chunked and no Content-Length, so Content-Length-requiring OTLP endpoints (Azure Monitor DCE et al.) still reject every batch.

If the fix is merged but unreleased, could you note the target version here? Otherwise, please reopen — happy to re-verify against any build.

CompPhy · 1 month ago

Agreed. Telemetry is an active problem for anyone trying to use 3P right now; in both Code and Desktop deployments. This is basically the reliable method for getting per-user metrics and other information, but we can't use it right now. We have also been testing every release looking for this fix.

DamianKomorowski · 1 month ago

@bhosmer-ant Why was the ticket closed? Unfortunately, the problem persists in version 2.1.211

velimattiv · 1 month ago

Confirmed fixed in 2.1.212 ✅ — verified with the same header-capture method used earlier in this thread (nested claude -p with an OTLP logs endpoint repointed at a local capture server via --settings).

Fix #1 — Content-Length regression: the OTLP/HTTP logs export now sends a proper Content-Length and no Transfer-Encoding: chunked:

2.1.211 (broken):  POST http/1.1  transfer-encoding: chunked   content-length: —      ua: OTel-OTLP-Exporter-JavaScript/0.208.0
2.1.212 (fixed):   POST http/1.1  transfer-encoding: —         content-length: 11656  ua: OTel-OTLP-Exporter-JavaScript/0.208.0

That unblocks Azure Monitor DCE and other endpoints that reject chunked OTLP/HTTP (the MissingContentLengthHeader 400 is gone).

Fix #2 — missing trace_id/span_id with TRACEPARENT: also confirmed — with TRACEPARENT set in headless/-p mode, the exported log records now carry the expected trace_id and span_id (both traceparent id bytes present in the captured payload).

Thanks for landing both — we can now retire the local Content-Length forwarder shim we'd been running as a workaround. 🙏

CompPhy · 1 month ago

I'll do even better here... we just confirmed that this also works with claude-apps-gateway and telemetry: forward_to:. This fix makes the claude-apps-gateway a viable path to get per-user data, etc. now; because it also tags the user information from it's authentication cache.