You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Apps gateway serves Claude Desktop an otlpEndpoint pointing at its own bearer-gated OTLP ingest but no otlpHeaders, so every Desktop telemetry flush is rejected with missing_token #82092
I have searched existing issues and this hasn't been reported yet
This is a single bug report (please file separate reports for different bugs)
I am using the latest version of Claude Code
What's Wrong?
Summary
A Claude apps gateway with telemetry.forward_to configured and a desktop block in a managed policy serves Claude Desktop a GET /user/bootstrap response containing otlpEndpoint set to listen.public_url — the gateway's own origin — and no otlpHeaders.
The gateway's OTLP ingest endpoints require a bearer token (POST /v1/metrics, /v1/logs, /v1/traces, per the gateway protocol reference). otlpHeaders is the only mechanism Claude Desktop has for attaching credentials to its OTLP exports. Because bootstrap populates the endpoint but not the headers, Desktop posts unauthenticated to an endpoint that mandates authentication, and 100% of Desktop telemetry is dropped at the door.
No client-side or MDM setting can fix this: the credential the endpoint wants is a per-user device-flow token with (in our case) a 1-hour TTL, and MDM otlpHeaders values are necessarily static.
Claude Code CLI telemetry through the same gateway works correctly — it authenticates with its session bearer and the gateway relays to forward_to. Desktop is the only affected client.
Environment
Gateway server: claude 2.1.220, self-hosted (ECS Fargate behind an ALB)
Claude Desktop on Windows 11, managed via HKLM\SOFTWARE\Policies\Claude
Auth: OIDC, session.ttl_hours: 1
Upstream: Amazon Bedrock
Telemetry destination: OTLP/HTTP collector reached via telemetry.forward_to
Notes / supporting detail
The gateway protocol reference documents the telemetry endpoints as POST /v1/metrics, /v1/logs, /v1/traces(bearer), and states that when connected to a gateway the client sends telemetry there and ignores OTEL_EXPORTER_OTLP_* env vars — so the env-var path documented for CLI clients isn't available as a workaround either.
The gateway config reference notes that telemetry.forward_to + listen.public_url enables telemetry and that the CLI stamps user.id/user.email/user.groups from the JWT. Desktop instead receives enduser.id as a resource attribute, so even once ingest is unblocked, Desktop and CLI telemetry arrive under different attribute names and don't aggregate on the same dashboards without a collector-side rename.
For contrast, AWS's reference implementation of a third-party bootstrap server (aws-solutions-library-samples/guidance-for-claude-code-with-amazon-bedrock, PRs Regression: File Write Protection Not Re-Enabled After /compact Command #669/It still ask for Git User Details #670) returns otlpEndpoint together with otlpHeaders carrying per-user identity, and points the endpoint at a collector rather than at the bootstrap server itself. That appears to be the intended shape of a bootstrap response; the first-party gateway does neither.
Why there is no client-side workaround
An MDM otlpHeaders value cannot substitute for the missing one. Per the bootstrap server docs:
When a bootstrap response is available, it is the effective configuration. The MDM profile supplies the trust anchor (bootstrapUrl, optional bootstrapOidc, and the bootstrapEnabled opt-out), and Claude Desktop does not consult MDM for any key the bootstrap server is permitted to set. A bootstrap-settable key that your response omits is treated as unset, not inherited from MDM, so return every key you want applied.
otlpHeaders is marked MDM + Bootstrap in the configuration reference, so it is bootstrap-settable and an MDM value for it is ignored while bootstrapUrl is configured. Combined with the gateway not emitting the key, there is no path by which Desktop can obtain an OTLP credential. The only ways out are both unattractive:
Terminate the OTLP paths at the load balancer ahead of the gateway and accept them unauthenticated, since no credential can be delivered to distinguish legitimate clients.
Abandon bootstrap for Desktop (bootstrapEnabled: false) and configure the whole client through MDM, which restores otlpHeaders but gives up per-group policy, the gateway-derived model list, and per-user config delivery — the reasons to run a bootstrap server at all.
Neither preserves both authenticated telemetry ingest and gateway-delivered policy, which is why this reads as a gap rather than a configuration mistake.
What Should Happen?
One of:
Bootstrap includes a usable credential — e.g. otlpHeaders: {"Authorization": "Bearer <session token>"}. This looks like the natural fix: the response already carries expiresAt and Desktop refetches bootstrap periodically (~30 min in our observation), comfortably inside a 1-hour session.ttl_hours, so the token would rotate on the existing refresh cycle.
The gateway accepts Desktop's exports without a bearer on a distinguishable basis, or supports a second inbound auth mode for the OTLP paths (a static ingest token configurable in gateway.yaml). There is currently no such key — telemetry.forward_to[].headers is outbound-only and access_control is CIDR-based.
Bootstrap omits otlpEndpoint when it can't supply a credential, so Desktop falls back to the MDM-configured endpoint instead of silently failing against the gateway.
At minimum, the gateway should not advertise an endpoint it will then reject, and the mismatch should be visible: today it is silent from the operator's perspective unless someone reads the ingest logs, and silent from the user's perspective entirely.
Error Messages/Logs
The bootstrap response:
{
"inferenceProvider": "gateway",
"inferenceGatewayBaseUrl": "https://claude-gateway.example.internal",
"inferenceGatewayAuthScheme": "sso",
"otlpEndpoint": "https://claude-gateway.example.internal",
"otlpProtocol": "http/json",
"otlpResourceAttributes": { "enduser.id": "[email protected]" },
"inferenceModels": [ "...5 models..." ],
"expiresAt": 1785254860
}
Note `otlpEndpoint` is present and `otlpHeaders` is absent.
Every subsequent Desktop flush is refused:
{"ts":"2026-07-28T14:13:09.918Z","evt":"auth.denied","request_id":"...","reason":"missing_token","path":"/v1/logs","client_ip":"..."}
{"ts":"2026-07-28T14:35:22.104Z","evt":"auth.denied","request_id":"...","reason":"missing_token","path":"/v1/metrics","client_ip":"..."}
Meanwhile `desktop_bootstrap.serve` events for the same user return 200 throughout, so this isn't a policy-matching or opt-in problem — bootstrap is working, it just hands over an unusable telemetry configuration.
Steps to Reproduce
Configure a gateway with both listen.public_url and telemetry.forward_to (this is the documented pair that enables telemetry), plus a managed policy carrying a desktop block so GET /user/bootstrap serves instead of 404ing:
Point Claude Desktop at it with bootstrapUrl + bootstrapEnabled via managed configuration and sign in. Bootstrap succeeds — the user gets the model list and gateway inference works.
Watch the gateway logs as Desktop flushes telemetry.
Preflight Checklist
What's Wrong?
Summary
A Claude apps gateway with
telemetry.forward_toconfigured and adesktopblock in a managed policy serves Claude Desktop aGET /user/bootstrapresponse containingotlpEndpointset tolisten.public_url— the gateway's own origin — and nootlpHeaders.The gateway's OTLP ingest endpoints require a bearer token (
POST /v1/metrics,/v1/logs,/v1/traces, per the gateway protocol reference).otlpHeadersis the only mechanism Claude Desktop has for attaching credentials to its OTLP exports. Because bootstrap populates the endpoint but not the headers, Desktop posts unauthenticated to an endpoint that mandates authentication, and 100% of Desktop telemetry is dropped at the door.No client-side or MDM setting can fix this: the credential the endpoint wants is a per-user device-flow token with (in our case) a 1-hour TTL, and MDM
otlpHeadersvalues are necessarily static.Claude Code CLI telemetry through the same gateway works correctly — it authenticates with its session bearer and the gateway relays to
forward_to. Desktop is the only affected client.Environment
claude2.1.220, self-hosted (ECS Fargate behind an ALB)HKLM\SOFTWARE\Policies\Claudesession.ttl_hours: 1telemetry.forward_toNotes / supporting detail
POST /v1/metrics,/v1/logs,/v1/traces(bearer), and states that when connected to a gateway the client sends telemetry there and ignoresOTEL_EXPORTER_OTLP_*env vars — so the env-var path documented for CLI clients isn't available as a workaround either.telemetry.forward_to+listen.public_urlenables telemetry and that the CLI stampsuser.id/user.email/user.groupsfrom the JWT. Desktop instead receivesenduser.idas a resource attribute, so even once ingest is unblocked, Desktop and CLI telemetry arrive under different attribute names and don't aggregate on the same dashboards without a collector-side rename.aws-solutions-library-samples/guidance-for-claude-code-with-amazon-bedrock, PRs Regression: File Write Protection Not Re-Enabled After /compact Command #669/It still ask for Git User Details #670) returnsotlpEndpointtogether withotlpHeaderscarrying per-user identity, and points the endpoint at a collector rather than at the bootstrap server itself. That appears to be the intended shape of a bootstrap response; the first-party gateway does neither.Why there is no client-side workaround
An MDM
otlpHeadersvalue cannot substitute for the missing one. Per the bootstrap server docs:otlpHeadersis markedMDM + Bootstrapin the configuration reference, so it is bootstrap-settable and an MDM value for it is ignored whilebootstrapUrlis configured. Combined with the gateway not emitting the key, there is no path by which Desktop can obtain an OTLP credential. The only ways out are both unattractive:bootstrapEnabled: false) and configure the whole client through MDM, which restoresotlpHeadersbut gives up per-group policy, the gateway-derived model list, and per-user config delivery — the reasons to run a bootstrap server at all.Neither preserves both authenticated telemetry ingest and gateway-delivered policy, which is why this reads as a gap rather than a configuration mistake.
What Should Happen?
One of:
otlpHeaders: {"Authorization": "Bearer <session token>"}. This looks like the natural fix: the response already carriesexpiresAtand Desktop refetches bootstrap periodically (~30 min in our observation), comfortably inside a 1-hoursession.ttl_hours, so the token would rotate on the existing refresh cycle.gateway.yaml). There is currently no such key —telemetry.forward_to[].headersis outbound-only andaccess_controlis CIDR-based.otlpEndpointwhen it can't supply a credential, so Desktop falls back to the MDM-configured endpoint instead of silently failing against the gateway.At minimum, the gateway should not advertise an endpoint it will then reject, and the mismatch should be visible: today it is silent from the operator's perspective unless someone reads the ingest logs, and silent from the user's perspective entirely.
Error Messages/Logs
The bootstrap response: { "inferenceProvider": "gateway", "inferenceGatewayBaseUrl": "https://claude-gateway.example.internal", "inferenceGatewayAuthScheme": "sso", "otlpEndpoint": "https://claude-gateway.example.internal", "otlpProtocol": "http/json", "otlpResourceAttributes": { "enduser.id": "[email protected]" }, "inferenceModels": [ "...5 models..." ], "expiresAt": 1785254860 } Note `otlpEndpoint` is present and `otlpHeaders` is absent. Every subsequent Desktop flush is refused: {"ts":"2026-07-28T14:13:09.918Z","evt":"auth.denied","request_id":"...","reason":"missing_token","path":"/v1/logs","client_ip":"..."} {"ts":"2026-07-28T14:35:22.104Z","evt":"auth.denied","request_id":"...","reason":"missing_token","path":"/v1/metrics","client_ip":"..."} Meanwhile `desktop_bootstrap.serve` events for the same user return 200 throughout, so this isn't a policy-matching or opt-in problem — bootstrap is working, it just hands over an unusable telemetry configuration.Steps to Reproduce
Configure a gateway with both
listen.public_urlandtelemetry.forward_to(this is the documented pair that enables telemetry), plus a managed policy carrying adesktopblock soGET /user/bootstrapserves instead of 404ing:Point Claude Desktop at it with
bootstrapUrl+bootstrapEnabledvia managed configuration and sign in. Bootstrap succeeds — the user gets the model list and gateway inference works.Watch the gateway logs as Desktop flushes telemetry.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
No response
Claude Code Version
2.1.220
Platform
AWS Bedrock
Operating System
Windows
Terminal/Shell
Other
Additional Information
No response