Repository navigation
Feature Request: Enterprise OTel auth — mTLS env vars + dynamic-headers helper (parity with Claude Code) #3477
Description
Summary
Copilot CLI's OpenTelemetry support (shipped via #2471) authenticates the OTLP
exporter via a static OTEL_EXPORTER_OTLP_HEADERS value only. This blocks
production enterprise deployments that need (a) mutual TLS to secured OTel
endpoints, or (b) automatic refresh of expiring auth tokens for Microsoft Entra
ID / managed-identity flows.
The peer product Claude Code already ships both. This request asks for parity.
The "parity with Claude Code" framing has precedent here — #2471 was titled
exactly that, and shipped Copilot CLI's OTel support in v1.0.4. This request
asks for the auth-side parity that the initial scope deferred.
What Copilot CLI has today
- Native OTLP emission (since v1.0.4, OTel telemetry support for Copilot CLI (parity with Claude Code) #2471).
- Static
OTEL_EXPORTER_OTLP_HEADERS, set at process start, never refreshed. - HTTP/JSON transport only (no protobuf — see Support protobuf OTLP export (OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf) #2934).
- The official Copilot SDK observability docs document a
TelemetryConfigobject withotlpEndpoint,exporterType("otlp-http"/"file"),sourceName,captureContent— noheadersfield, no auth surface, no mTLS. The auth gap exists at the SDK API surface too, not just the CLI env-var surface.
What Claude Code has and Copilot CLI doesn't
1. mTLS via env vars
Claude Code honours the standard OpenTelemetry mTLS configuration env vars.
From their monitoring docs:
| Protocol | Client certificate variables | CA trust |
|---|---|---|
http/protobuf, http/json |
CLAUDE_CODE_CLIENT_CERT, CLAUDE_CODE_CLIENT_KEY, optionally CLAUDE_CODE_CLIENT_KEY_PASSPHRASE |
NODE_EXTRA_CA_CERTS |
grpc |
OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE (plus per-signal variants) |
OTEL_EXPORTER_OTLP_CERTIFICATE |
The OTEL_EXPORTER_OTLP_CLIENT_* and OTEL_EXPORTER_OTLP_CERTIFICATE variables
are documented in the
OpenTelemetry specification.
The bundled @opentelemetry/exporter-trace-otlp-* packages already support
these at the SDK level — they just aren't surfaced from Copilot CLI.
2. Dynamic headers / token refresh
Claude Code lets administrators configure a script that returns headers as
JSON, run at startup and periodically thereafter (default every 29 minutes).
From their docs:
// .claude/settings.json
{
"otelHeadersHelper": "/bin/generate_opentelemetry_headers.sh"
}#!/bin/bash
# Mint a fresh Microsoft Entra Bearer for Azure Monitor every time the helper runs
echo "{\"Authorization\": \"Bearer $(az account get-access-token \
--scope https://monitor.azure.com/.default --query accessToken -o tsv)\"}"The interval is configurable via CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS.
Why this matters
Microsoft's recommended path for OTLP ingestion into Azure Monitor /
Application Insights uses Microsoft Entra Bearer tokens
(docs).
Entra access tokens are short-lived (60–90 min by default; up to 24h via
Configurable Token Lifetime policies). Without a refresh mechanism in Copilot
CLI, long-running sessions hit auth failures mid-session.
Today the workaround is for each enterprise to build a per-organisation
OTel-translation bridge that takes a static header from Copilot and exchanges
it for a refreshed Entra Bearer upstream. That is real operational overhead
for what is essentially missing config surface in Copilot CLI itself — the
underlying SDK already supports both auth modes.
The similar request on the VS Code side
(microsoft/vscode#304505)
asks for the same dynamic-headers capability, but the CLI is a distinct entry
point and warrants its own implementation.
Specific asks (small, well-scoped)
-
Honour the standard OTel mTLS env vars in Copilot CLI's OTLP exporter —
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,OTEL_EXPORTER_OTLP_CLIENT_KEY,
OTEL_EXPORTER_OTLP_CERTIFICATE, plus per-signal variants
(OTEL_EXPORTER_OTLP_TRACES_*/_METRICS_*/_LOGS_*). The bundled
@opentelemetry/exporter-trace-otlp-*packages implement these env vars
per the OpenTelemetry spec — Copilot CLI needs a small wiring change to
thread them into the exporter constructor. -
Add a dynamic-headers script hook — analogous to Claude Code's
otelHeadersHelper. A config setting points at a script; the script
outputs a JSON object of headers; runs at startup and on a configurable
interval (suggested default: 29 minutes, matching Claude Code). HTTP
transport only (matches Claude Code's design).
Why it's small
Both asks pass through existing OpenTelemetry SDK functionality. The first is
a small wiring change in the exporter setup. The second is a small wrapper
that periodically execs a script and rebuilds the exporter's headers —
Claude Code's implementation is the existing reference design.
Related
- Support protobuf OTLP export (OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf) #2934 — Support protobuf OTLP export (related transport-side gap).
- microsoft/vscode#304505 — Same feature request, VS Code side.
- Ingest OTLP into Azure Monitor.
- Claude Code — Monitoring.
- OTel specification — Exporter configuration.
Activity
velimattiv commentedon Jun 8, 2026
Follow-up from the author — a worked example of the workaround this gap forces, from a live enterprise deployment.
Since filing this, I've had to actually build the "per-organisation OTel-translation bridge" I described above as the workaround, to get Copilot CLI telemetry into Microsoft Azure Monitor / Log Analytics authenticated with Microsoft Entra ID for enterprise clients. Documenting it here both to strengthen the case and to give anyone else hitting this a replication recipe for any endpoint that needs protobuf-over-HTTP + a refreshed bearer.
Two gaps compound here, not one. This issue (#3477) is the auth-refresh half; #2934 is the transport half. Azure Monitor's OTLP ingest is protobuf-only — it returns
HTTP 415to OTLP/HTTP JSON — and Entra access tokens are short-lived (60–90 min by default). Copilot CLI today offers only OTLP/HTTP JSON with a staticOTEL_EXPORTER_OTLP_HEADERS. So the native exporter is unusable against Azure on both axes at once: wrong wire format and a header that's dead before a long session ends. Claude Code clears both — it emitshttp/protobufnatively and shipsotelHeadersHelper— which is exactly why the parity framing in the original request is the right one.The shim we had to engineer. Because the OTLP exporter couldn't reach Azure, we fell back to Copilot's file exporter (
COPILOT_OTEL_FILE_EXPORTER_PATH) and wrote a small local file-proxy / forwarder — a few hundred lines of zero-dependency Node — that does the job the exporter should do natively. The generic recipe, for anyone replicating against a protobuf+bearer endpoint:{offset, inode}; if the inode changes or the file shrank, reset to 0 — the CLI recreates the file across upgrades).protobufjs). Filter to the single span that carries authoritative token counts and exclude the parent rollup span, or you double-count 2×.401/403with a forced re-mint. This is precisely what Feature Request: Enterprise OTel auth — mTLS env vars + dynamic-headers helper (parity with Claude Code) #3477 asks the CLI to do — reimplemented in user space because a static header silently starts dropping telemetry the instant it expires.application/x-protobufto the ingest endpoint.The load-bearing, genuinely fiddly parts are the offset/inode bookkeeping (1) and the refresh-with-retry (4) — both are pure consequences of the two missing config surfaces, not anything intrinsic to the data.
Why fixing this upstream is strictly better than the shim. The forwarder carries real, ongoing operational cost that exists only because the exporter can't be configured: a singleton-per-container lifecycle wired through session hooks, offset persistence across crashes, dedup on re-forward, demux of concurrent sessions, and a bespoke protobuf encoder we now own indefinitely. All of it evaporates the day Copilot CLI honours a dynamic-headers hook (#3477) and protobuf transport (#2934) — at which point we'd simply point the native exporter at Azure with an
otelHeadersHelper-style script, exactly as we already do for Claude Code. The underlying@opentelemetry/exporter-*packages already support both modes; this remains a small wiring change that saves every enterprise from rebuilding the same bridge downstream.Happy to share the refresh-with-retry helper contract or the transcoder field-mapping if it's useful to whoever picks up the implementation.
velimattiv commentedon Jul 14, 2026
The 2026-07-08 enterprise-managed OTel export moved the dial here, but did not close this issue.
The enterprise-managed OpenTelemetry export is a real step — a managed
telemetryblock (endpoint, transport, resource attributes, and exporter headers) delivered via MDM / server-managed settings /managed-settings.json, applied to both Chat and the CLI agent host. That's most of what enterprise OTel needs.But the header it sets is still static — configured once, never refreshed — which is precisely the blocker this issue raises. The documented target for direct Azure Monitor / Log Analytics OTLP ingestion is a Microsoft Entra bearer that expires in ~60–90 min, so long sessions still fail mid-stream, and every enterprise is still forced to stand up a per-org bridge/collector that swaps the static header for a refreshed Entra token upstream. Eliminating that bridge — native OTLP → Azure Monitor with a refreshing credential — is the whole point of the dynamic-headers hook requested here (parity with Claude Code's
otelHeadersHelper, ~29-min refresh).Now that the managed-settings surface exists, the natural fix is to let it carry a refresh mechanism — e.g. a managed setting pointing at a headers-helper script (or accepting the standard
OTEL_EXPORTER_OTLP_HEADERSrefresh pattern), run on an interval.Related: the VS Code Chat-side equivalent, microsoft/vscode#304505 ("Entra Id auth header support for OTel ingestion"), was closed as not planned — so this remains the live, cross-surface thread for the refresh gap. The 07-08 release adding managed headers without refresh is exactly why it's still needed.
patniko commentedon Oct 26, 2026
Thanks for the detailed request and the worked example. Both pieces are now available: Copilot CLI v1.0.61 added mTLS and private-CA support for OTLP export over HTTPS via the standard
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,OTEL_EXPORTER_OTLP_CLIENT_KEYandOTEL_EXPORTER_OTLP_CERTIFICATEvariables (plushttp/protobufexport viaOTEL_EXPORTER_OTLP_PROTOCOL), and v1.0.100 added a telemetry headers command, configurable in user or enterprise-managed settings, that runs a helper you provide and periodically refreshes the OTLP authentication headers, which covers short-lived tokens such as Entra access tokens. Seecopilot help monitoringandcopilot help environmentfor configuration details. If header refresh doesn't work for your Entra setup on the latest version, please reopen with details.Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.