[BUG] `env` block in managed-settings.json does not apply OTEL env vars — initialization order issue

Status Open
Reported on v2.1.170
Maintainer reply None cached
Activity 7 comments · opened Jun 11, 2026

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?

Summary

env keys in /Library/Application Support/ClaudeCode/managed-settings.json do not take effect for OpenTelemetry configuration. The OTEL SDK initializes before managed settings are read, so OTEL-related env vars set via the env block arrive too late.

Environment

Claude Code: 2.1.170 (desktop-bundled) / 2.1.173 (system)
Desktop app: 1.11847.5
OS: macOS darwin 25.5.0 arm64
Deployment: MDM-managed enterprise
Managed settings file

Path: /Library/Application Support/ClaudeCode/managed-settings.json
Permissions: -rw-r--r-- root wheel ✓

{
"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>",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <token>",
"OTEL_LOG_TOOL_DETAILS": "1"
}
}
How the desktop app spawns the CLI (ps aux):

claude \
--setting-sources=user,project,local \
--settings {} \
...
managed is absent from --setting-sources; --settings {} is empty.

Root cause

The desktop app sets OTEL env vars at the OS level before exec'ing the CLI:

OTEL_SERVICE_NAME=claude-code-desktop
OTEL_RESOURCE_ATTRIBUTES=service.name=claude-code-desktop,...
The Node.js OTEL SDK initializes at module load using these OS-level vars. When the CLI later reads managed-settings.json and does process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "...", the SDK is already initialized — the value has no effect.

The --setting-sources help text says "Admin-managed (policy) settings still apply" but lists only auth, model selection, and built-in tools — not the env block.

What Should Happen?

Expected behavior:

Managed-settings env vars are applied before OTEL SDK initialization so enterprise deployments can route telemetry via MDM without OS-level launchd injection.

Error Messages/Logs

No error is surfaced. The failure is silent.

# Verify managed settings file is present and readable
$ ls -la "/Library/Application Support/ClaudeCode/managed-settings.json"
-rw-r--r--  1 root  wheel  391 Jun 11 11:48 managed-settings.json

# File parses as valid JSON — no error
$ python3 -c "import json; json.load(open('/Library/Application Support/ClaudeCode/managed-settings.json')); print('valid')"
valid

# Spawned CLI process environment — OTEL vars from managed-settings.json are absent
$ ps eww -p <claude-pid> | tr ' ' '\n' | grep -E "OTEL|CLAUDE_CODE_ENABLE"
OTEL_SERVICE_NAME=claude-code-desktop
OTEL_RESOURCE_ATTRIBUTES=service.name=claude-code-desktop,service.version=1.11847.5,...
# ^ Only desktop app's own OTEL vars. No OTEL_EXPORTER_OTLP_* or CLAUDE_CODE_ENABLE_TELEMETRY.

# Attempting to use 'managed' as a --setting-sources value confirms it is not a recognized source
$ claude --setting-sources=managed --print "test"
Error processing --setting-sources: Invalid setting source: managed. Valid options are: user, project, local
No OTEL initialization logs appear. No telemetry reaches the configured collector endpoint. No warning or error is logged to indicate that env vars from managed-settings.json were ignored.

Steps to Reproduce

  1. Deploy managed-settings.json via MDM to /Library/Application Support/ClaudeCode/managed-settings.json with the following content:

{
"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://<your-collector>",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <token>",
"OTEL_LOG_TOOL_DETAILS": "1"
}
}
Permissions: -rw-r--r-- root wheel (world-readable)

  1. Launch the Claude Code desktop app (v1.11847.5, macOS).
  2. Start any conversation to trigger a CLI subprocess spawn.
  3. Observe the spawn command via ps aux | grep claude:

.../claude-code/2.1.170/.../claude \
--setting-sources=user,project,local \
--settings {} \
...
Note: managed is absent from --setting-sources; --settings is empty {}.

  1. Inspect the spawned process environment:

ps eww -p <claude-pid> | tr ' ' '\n' | grep -E "OTEL|CLAUDE_CODE_ENABLE_TELEMETRY"
Observed: Only OTEL_SERVICE_NAME=claude-code-desktop and OTEL_RESOURCE_ATTRIBUTES=... (set by the desktop app itself). None of the OTEL_EXPORTER_OTLP_* vars or CLAUDE_CODE_ENABLE_TELEMETRY from managed-settings.json are present.
6.Verify the managed-settings file is readable and valid:cat "/Library/Application Support/ClaudeCode/managed-settings.json" # parses OK
python3 -c "import json; json.load(open('/Library/Application Support/ClaudeCode/managed-settings.json')); print('valid')"

  1. Confirm no telemetry reaches the configured OTEL endpoint (zero accepted log records / metric points on collector side).

Expected: OTEL env vars from managed-settings.json env block are applied before OTEL SDK initialization; telemetry is sent to the configured endpoint.

Actual: OTEL SDK initializes using only the desktop app's own env vars. Managed-settings env vars are applied after SDK init and have no effect.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.173 (system CLI) / 2.1.170 (desktop-bundled CLI that actually runs during sessions).

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Version discrepancy: The desktop app bundles and spawns its own CLI (claude-code/2.1.170) rather than the system-installed CLI (2.1.173). Both versions exhibit the same behavior.

managed is not a valid --setting-sources value: Running claude --setting-sources=managed returns Invalid setting source: managed. Valid options are: user, project, local. This confirms managed settings are loaded through a separate code path — but that path does not apply the env block before OTEL SDK initialization.

The desktop app has a parallel enterprise config channel: The renderer process receives --desktop-enterprise-config={"disableEssentialTelemetry":false,"disableNonessentialTelemetry":false,...} as a CLI flag. This suggests the desktop app has its own enterprise settings pipeline that is distinct from managed-settings.json. It is unclear whether env keys from managed-settings.json are ever intended to flow through this channel.

Related open issues: This appears to be the same root cause as #66401 (macOS, TUI) and #67339 (Windows Desktop), where OTEL env vars set via settings files are confirmed read but produce no telemetry output.

View original on GitHub ↗

6 Comments

github-actions[bot] · 2 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/67339
  2. https://github.com/anthropics/claude-code/issues/46204

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

dominicbalabat · 2 months ago

This bug occured in MacOS.

abhinas90 · 2 months ago

This is a common enterprise rollout friction point. A few things to check:

  1. Verify the proxy chain: Claude Code respects \HTTP_PROXY\/\HTTPS_PROXY\ but NOT \NO_PROXY\ consistently across all internal tools (particularly npm subprocesses).
  1. Certificate trust store: if your org uses a custom CA, check whether Node.js is picking it up — \NODE_EXTRA_CA_CERTS\ often needs to be set explicitly even when the system trust store has it.
  1. Auth token relay: corporate SSO tokens don't always survive the CLI→API gateway hop. The \ANTHROPIC_AUTH_TOKEN\ env var and session cache mismatch is a recurring source.
  1. Network segmentation: if your proxy requires Kerberos or NTLM, standard Node.js HTTP agents won't negotiate — you need a CNTLM or similar local proxy bridge.

I maintain a enterprise deployment hardening playbook covering these — let me know if useful.

kentloog · 1 month ago

Confirming the same behavior on another MDM-managed enterprise fleet, macOS (darwin 25.5.0 arm64), Desktop app 1.22209.0 (bundled CLI 2.1.209), reproduced with a payload capture against a local OTLP listener.

Setup: /Library/Application Support/ClaudeCode/managed-settings.json with an env block setting OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, and OTEL_RESOURCE_ATTRIBUTES (deployment.type=...,user.email=...). No OTEL vars in shell env. Same file exercised by both the standalone CLI (2.1.193) and the Desktop app.

Result, side by side:

  • CLI metrics resource: deployment.type, user.email, host.arch, os.type, os.version, service.name=claude-code, service.version — managed-settings resource attributes applied correctly.
  • Desktop metrics resource: service.name=claude-code-desktop, service.version (app version), claude.deployment_mode=1p, host.arch, os.type, os.version — the configured OTEL_RESOURCE_ATTRIBUTES are absent.

The endpoint and the auth header from the SAME env block ARE applied by the Desktop app (the request reached the configured backend with the configured x-api-key), so it is not the whole block being ignored. This matches the suspected initialization order: the OTEL Resource is built before the managed env is applied, while the exporter endpoint/headers are read later.

Two additional observations:

  • Current Desktop builds DO emit identity (user.email, user.id, user.account_uuid, user.account_id, organization.id, session.id) as data-point attributes on each metric, same as the CLI. So the gap is specifically the resource: backends that attribute by resource attributes (per most vendor integrations built on the documented OTEL_RESOURCE_ATTRIBUTES contract) see unattributed metrics, and custom resource attributes (e.g. our deployment.type) are unrecoverable since they exist only via OTEL_RESOURCE_ATTRIBUTES.
  • Workaround: exporting OTEL_RESOURCE_ATTRIBUTES in the user's shell profile works, the Desktop app picks it up at launch. Notably, for the Desktop app, shell env overrides managed-settings values, which is the opposite of the CLI precedence.

Impact for enterprise fleets: per-user telemetry rollout via managed settings works for CLI users but leaves every Desktop user's metrics without resource-level identity. A fix for the init order, or applying managed env before OTEL Resource construction, would unblock fleet-wide OTEL rollouts.

AlexMabry · 1 month ago

Adding a data point that isolates this to the surface rather than the settings file, the platform, or the version: on the same machine, in the same hour, with one app build reading one managed-settings.json, the Cowork surface applies the managed OTEL_RESOURCE_ATTRIBUTES and the Code tab does not.

We deploy a single static resource attribute (team=<id>) per plan via the env block of a MDM-managed managed-settings.json, and aggregate on it server side. One user, macOS, Desktop app 1.24012.1, same 2-hour window:

| service.name | service.version | datapoints | with team |
|---|---|---|---|
| cowork | 1.24012.1 | 894 | 894 |
| claude-code-desktop | 1.24012.1 | 304 | 0 |

Same app, same config file, same process owner, opposite outcome. That rules out the settings file being unreadable or malformed, and it rules out a version floor.

Fleet-wide over the same window, every surface pointed at the same collector:

| service.name | datapoints | with team | users |
|---|---|---|---|
| claude-code (CLI) | 16,865 | 16,865 | 110 |
| cowork | 1,595 | 1,595 | 10 |
| claude-code-desktop | 4,418 | 0 | 25 |

Not macOS-specific. claude-code-desktop emits zero team on every platform and build we see: windows/amd64 (1.24012.9, 1.24012.1), darwin/arm64 (1.24012.9, 1.24012.1), linux/amd64 (1.24012.9, 1.21459.0, 1.17377.1). All carry claude.deployment_mode=1p. Cowork is 100% tagged on windows/amd64 and darwin/arm64 for the same builds. Suggest widening the platform:macos label.

Two details consistent with the initialization-order root cause in the original report:

  1. OTEL_EXPORTER_OTLP_ENDPOINT and the Authorization header from the same env block are honored by claude-code-desktop — its metrics arrive at our collector, authenticated, and are otherwise well-formed. Only OTEL_RESOURCE_ATTRIBUTES is lost. So the block is not being ignored wholesale.
  2. The desktop app injects its own OTEL_RESOURCE_ATTRIBUTES (service.name=claude-code-desktop,service.version=…) into the spawned CLI's environment. That reads as an overwrite of the managed value rather than a merge, which would explain why this one key is the casualty while sibling keys survive. Cowork's exporter path evidently does not clobber it.

Impact is the same as described upstream: for backends that attribute by resource attributes, every Desktop Code tab session is unattributable, and a custom attribute like ours has no recovery path since it exists only via OTEL_RESOURCE_ATTRIBUTES. In our case that is 25 of 145 active users invisible to per-team reporting, with no client-side configuration that closes the gap.

Merging managed OTEL_RESOURCE_ATTRIBUTES with the app-injected ones, instead of overwriting, would fix it. The Cowork path on the same build looks like a working reference.

Tassiroo · 1 month ago

Still reproduces on much newer builds, and I have data that narrows the mechanism.

Versions

| | this report | original report |
|---|---|---|
| Claude.app | 1.24012.9 | 1.11847.5 |
| bundled Claude Code | 2.1.219 | 2.1.170 |
| OS | macOS 26.5.0, arm64 | darwin 25.5.0, arm64 |

The injection point is confirmed. Walking the process tree of a Desktop session:

PID 33656  Claude       cwd=/          OTEL_RESOURCE_ATTRIBUTES: absent
PID 34323  disclaimer   cwd=<project>  OTEL_RESOURCE_ATTRIBUTES: present   <-- injected here
PID 34324  claude       cwd=<project>  OTEL_RESOURCE_ATTRIBUTES: present

The app root does not carry the variable; the helper it spawns does. Value seen by the
Claude Code process (ps eww):

OTEL_SERVICE_NAME=claude-code-desktop
OTEL_RESOURCE_ATTRIBUTES=service.name=claude-code-desktop,service.version=1.24012.9,
                         claude.deployment_mode=1p,host.arch=arm64,os.type=darwin,
                         os.version=26.5.0,process.owner=<user>

Refinement: the exporter variables now work — only the Resource is still lost.

This report says the SDK initializes at module load, so any settings-supplied OTEL_*
value arrives too late, and lists OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS
among the casualties. On these versions those two do take effect: our Desktop sessions are
actively exporting to our own New Relic account using endpoint and headers supplied from org
settings — verified by an established connection from the claude process to New Relic's OTLP
ingest on :4318.

OTEL_RESOURCE_ATTRIBUTES is the only one still lost.

That's consistent with the SDK's Resource being captured once at SDK init, while exporter
config is read later when the exporter is constructed. So the ordering problem looks partly
fixed already, and what remains is specific to the Resource — a narrower fix than the issue
currently describes.

Control test: env blocks are applied on Desktop; only occupied names are lost.

Two probe variables were added to env blocks using names nothing else sets — one at user
scope (~/.claude/settings.json), one at project-local scope (.claude/settings.local.json).
Both resolved correctly in a Desktop session.

So this is not "Desktop ignores env". It is specifically that a name already present in the
inherited environment is not overridden. That also explains why the exporter vars work
(nothing pre-sets them) and why OTEL_RESOURCE_ATTRIBUTES doesn't (the app does).

Scope is broader than managed-settings.json. Same failure via project-scope
.claude/settings.json. Ours sets OTEL_RESOURCE_ATTRIBUTES=project=<org>/<repo> so per-repo
metrics can be told apart; it works under the standalone CLI (2.1.220, same machine, same
settings file) and is silently dropped in the Desktop app. Desktop sessions therefore land in
our New Relic as service.name=claude-code-desktop with no project attribute, invisible in
any project-filtered dashboard, while CLI users appear normally.

Suggested fix. The app already has a private channel for host metadata — this session
carries CLAUDE_CODE_ENTRYPOINT=claude-desktop, CLAUDE_CODE_HOST_SESSION_ID,
CLAUDE_CODE_EXECPATH and others. CLAUDE_CODE_ENTRYPOINT alone is enough to derive
service.name=claude-code-desktop; only the app version would need a new carrier (e.g.
CLAUDE_CODE_HOST_VERSION). Claude Code could then build the OTel Resource internally and
merge it with env-provided attributes, instead of the app claiming a standards-defined,
user-owned variable outright. OTEL_RESOURCE_ATTRIBUTES is a comma-separated list precisely so
several parties can contribute — appending rather than assigning would fix this without the app
losing anything.

Possibly the same root cause: #42203 (Desktop ignores env.PATH, blamed on the sandbox —
PATH is also always already set) and #57026 (Windows Desktop, identical symptom, attributed
to oauthAccount hydration, closed as not planned).

Showing cached comments. Read the full discussion on GitHub ↗