[BUG] `env` block in managed-settings.json does not apply OTEL env vars — initialization order issue
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
- 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)
- Launch the Claude Code desktop app (v1.11847.5, macOS).
- Start any conversation to trigger a CLI subprocess spawn.
- 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 {}.
- 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')"
- 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.
Showing cached comments. Read the full discussion on GitHub ↗
6 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
This bug occured in MacOS.
This is a common enterprise rollout friction point. A few things to check:
HTTP_PROXY\/\HTTPS_PROXY\but NOT \NO_PROXY\consistently across all internal tools (particularly npm subprocesses).NODE_EXTRA_CA_CERTS\often needs to be set explicitly even when the system trust store has it.ANTHROPIC_AUTH_TOKEN\env var and session cache mismatch is a recurring source.I maintain a enterprise deployment hardening playbook covering these — let me know if useful.
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.jsonwith anenvblock settingOTEL_EXPORTER_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_HEADERS, andOTEL_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:
deployment.type,user.email,host.arch,os.type,os.version,service.name=claude-code,service.version— managed-settings resource attributes applied correctly.service.name=claude-code-desktop,service.version(app version),claude.deployment_mode=1p,host.arch,os.type,os.version— the configuredOTEL_RESOURCE_ATTRIBUTESare absent.The endpoint and the auth header from the SAME
envblock ARE applied by the Desktop app (the request reached the configured backend with the configuredx-api-key), so it is not the whole block being ignored. This matches the suspected initialization order: the OTEL Resource is built before the managedenvis applied, while the exporter endpoint/headers are read later.Two additional observations:
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 documentedOTEL_RESOURCE_ATTRIBUTEScontract) see unattributed metrics, and custom resource attributes (e.g. ourdeployment.type) are unrecoverable since they exist only viaOTEL_RESOURCE_ATTRIBUTES.OTEL_RESOURCE_ATTRIBUTESin 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
envbefore OTEL Resource construction, would unblock fleet-wide OTEL rollouts.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 managedOTEL_RESOURCE_ATTRIBUTESand the Code tab does not.We deploy a single static resource attribute (
team=<id>) per plan via theenvblock of a MDM-managedmanaged-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 | withteam||---|---|---|---|
|
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 | withteam| 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-desktopemits zeroteamon 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 carryclaude.deployment_mode=1p. Cowork is 100% tagged onwindows/amd64anddarwin/arm64for the same builds. Suggest widening theplatform:macoslabel.Two details consistent with the initialization-order root cause in the original report:
OTEL_EXPORTER_OTLP_ENDPOINTand theAuthorizationheader from the sameenvblock are honored byclaude-code-desktop— its metrics arrive at our collector, authenticated, and are otherwise well-formed. OnlyOTEL_RESOURCE_ATTRIBUTESis lost. So the block is not being ignored wholesale.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_ATTRIBUTESwith the app-injected ones, instead of overwriting, would fix it. The Cowork path on the same build looks like a working reference.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:
The app root does not carry the variable; the helper it spawns does. Value seen by the
Claude Code process (
ps eww):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_HEADERSamong 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
claudeprocess to New Relic's OTLPingest on :4318.
OTEL_RESOURCE_ATTRIBUTESis 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:
envblocks are applied on Desktop; only occupied names are lost.Two probe variables were added to
envblocks using names nothing else sets — one at userscope (
~/.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 theinherited environment is not overridden. That also explains why the exporter vars work
(nothing pre-sets them) and why
OTEL_RESOURCE_ATTRIBUTESdoesn't (the app does).Scope is broader than
managed-settings.json. Same failure via project-scope.claude/settings.json. Ours setsOTEL_RESOURCE_ATTRIBUTES=project=<org>/<repo>so per-repometrics 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-desktopwith noprojectattribute, invisible inany 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_EXECPATHand others.CLAUDE_CODE_ENTRYPOINTalone is enough to deriveservice.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 andmerge it with
env-provided attributes, instead of the app claiming a standards-defined,user-owned variable outright.
OTEL_RESOURCE_ATTRIBUTESis a comma-separated list precisely soseveral 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 —PATHis also always already set) and #57026 (Windows Desktop, identical symptom, attributedto
oauthAccounthydration, closed as not planned).