[BUG] policyHelper in /etc/claude-code/managed-settings.json is ignored on Linux when org server-managed (remote) settings are present

Status Open
Reported on v2.1.197
Maintainer reply None cached
Activity 5 comments · opened Jul 1, 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?

Per the docs, policyHelper configured in a system managed-settings.json should be executed at startup and its output used as the managed configuration for the run. On Linux, when the org's server-managed (remote) settings are present, the policyHelper executable is never invoked and the endpoint-managed file is ignored entirely; the run silently uses the cached remote settings instead.

What Should Happen?

Helper is executed, the emitted managed settings apply.

Error Messages/Logs

(none)

Steps to Reproduce

  1. Org account with server-managed settings (remote cache exists at ~/.claude/remote-settings.json).
  2. Create an executable helper that appends an invocation log and prints a valid managed-settings JSON envelope on stdout, e.g.:

{"managedSettings":{"hooks":{"SessionStart":[{"matcher":"*","hooks":[{"type":"command","command":"touch /tmp/applied.marker"}]}]}}}

  1. Verify standalone: running the helper prints the JSON and exits 0.
  2. Set /etc/claude-code/managed-settings.json to { "policyHelper": { "path": "/opt/bone-policy-probe.sh" } }.
  3. Run claude -p "reply: ok" --debug-file /tmp/cc-debug.log.

Claude Model

_No response_

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.197

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Terminal.app (macOS)

Additional Information

Claude is running in a linux container on Mac host.

View original on GitHub ↗

4 Comments

github-actions[bot] · 2 months ago

Found 1 possible duplicate issue:

  1. https://github.com/anthropics/claude-code/issues/70181

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

kristoffersingleton-yale · 1 month ago

Confirming on macOS (Darwin 25.5.0), Claude Code v2.1.205, with one
diagnostically significant difference from the original report: on this
platform/version the helper is executed — but its output is then
silently discarded in favor of the cached server-managed payload.

Evidence, using a canary methodology:

  1. Helper configured in root-owned /Library/Application Support/ClaudeCode/managed-settings.json:

``json
{
"policyHelper": {
"path": "/Library/Application Support/ClaudeCode/policy-helper.sh",
"timeoutMs": 10000,
"refreshIntervalMs": 0
}
}
``

  1. The helper (root-owned, 755, always exits 0) emits a distinctive canary

deny rule:

``json
{ "managedSettings": { "permissions": { "deny": ["Bash(policyhelper-canary-d34db33f *)"] } } }
``

  1. Fresh headless run (claude --debug-file /tmp/cc.log -p "ok"), then:
  • The helper's file access time updates to exactly the session start

— it ran (this differs from the original report's "never invoked" on
Linux/2.1.197; possibly version drift in where it dies).

  • grep canary /tmp/cc.log → nothing. The session's

Applying permission update ... destination 'policySettings' lines
contain only the org's server-delivered rules; the canary never
appears anywhere.

  • grep -i helper /tmp/cc.log → zero matches. No error, no validation

warning, no "output rejected" — the discard is completely silent.

  • Executing the helper standalone and piping to a JSON parser succeeds,

and the canary is present in stdout, so the envelope is well-formed.

Two adjacent observations from the same test matrix, in case they share a
root cause:

  • With a malformed policyHelper value (bare path string instead of the

object form), the key is stripped silently and the helper never runs —
expected fail-safe behavior, noted for contrast: the bug above occurs
with the valid object form.

  • Hooks delivered via any local managed source (system file,

managed-settings.d/, or helper output) never fire on this version while
managed permission rules do apply — same lineage as #33824.

Impact framing: administrators following the documented precedence
("policyHelper output: when configured, this is the only managed source
used") may believe posture-based policy is in force when it is not, with no
signal. Meanwhile the configured helper still executes at every startup, so
deployments get the feature's code-execution surface without its policy
effect. Suggested: implement the documented precedence, or emit an explicit
diagnostic when a configured helper's output is not applied, and gate the
docs section with the version where the precedence takes effect.

kristoffersingleton-yale · 1 month ago

Re the duplicate flag: not a strict duplicate of #70181 — that issue concerns an empty remote payload (304-cached) zeroing out endpoint-file deny/allow rules, contradicting the documented "first source that delivers a non-empty configuration" rule. This issue is a different rung of the same documented ladder: policyHelper is documented to preempt the remote payload outright, yet its output is not applied — on macOS/v2.1.205 the helper executes and its well-formed output is silently discarded (see canary reproduction above). Likely the same root-cause family (remote cache unconditionally winning regardless of the documented precedence), so cross-linking rather than merging preserves both reproductions.

JosselinHermann · 24 days ago

Confirming on macOS 15 (Darwin 25.5.0, arm64), Claude Code v2.1.223 and on
Windows, with a behavioural difference from the v2.1.205 report above: on both of
these the helper is never executed at all, matching the original Linux/v2.1.197
report rather than the runs-then-discards behaviour seen at v2.1.205.

macOS — method and result

Helper at /usr/local/bin/claude-policy, root:wheel 0755, instrumented to append a
timestamp to a marker file on every invocation:

#!/bin/sh
date +%s >> /tmp/claude-policy-ran
echo '{"managedSettings":{"permissions":{"defaultMode":"bypassPermissions"}}}'
exit 0

Referenced from root-owned /Library/Application Support/ClaudeCode/managed-settings.json:

{"policyHelper":{"path":"/usr/local/bin/claude-policy","timeoutMs":5000,"refreshIntervalMs":0}}

| Check | Result |
| :--- | :--- |
| Helper standalone | ✅ one line of stdout, parses as JSON, exit 0 |
| managed-settings.json valid | ✅ jq parses |
| Claude Code sees the file | ✅ Watching for changes in setting files ~/.claude/settings.json, /Library/Application Support/ClaudeCode/managed-settings.json... |
| MDM load | MDM settings load completed in 1ms |
| Marker file after claude doctor | ❌ never created |
| Marker file after full session (claude --debug-file … -p "…") | ❌ never created |
| Remote settings | still applied — Remote settings: Using cached settings (304) |

grep -i 'policy\|helper' over the debug log produces no reference to the configured
helper — no invocation, no error, no validation warning. Silent.

Org context: an account with server-managed settings configured, so the remote cache
at ~/.claude/remote-settings.json is populated.

Windows

Same construction via C:\Program Files\ClaudeCode\managed-settings.json pointing at a
.cmd helper. /permissions continued to report bypass permissions as "disabled by
settings"
, i.e. the server-delivered disableBypassPermissionsMode remained in force
and the helper's output never applied.

Because a plain POSIX sh helper reproduces the same negative result on macOS, this is
not a .cmd-vs-.exe executability problem.

Two notes for triage

  1. The platform:linux label looks too narrow. Reproduces on macOS and Windows.

Across the reports the failure point drifts by version — never-invoked at 2.1.197 and
2.1.223, runs-then-output-discarded at 2.1.205 — but the net effect is identical.

  1. Consistent with the cross-source exception list. The settings reference documents

a small set of keys honoured "when any admin-controlled managed source sets them"
(the sandbox allowlist locks, allowAllClaudeAiMcps, the sandbox binary paths,
forceRemoteSettingsRefresh, and — as of v2.1.223 — the env block merging per key).
Each of those looks like this same defect fixed for one key. policyHelper is
documented as preempting every managed source, yet is not on that list, which fits
the hypothesis in #70181 that the remote payload unconditionally wins whenever it
exists.

Requests

  • Implement the documented precedence, or emit an explicit diagnostic when a

configured helper's output is not applied. Silent discard means administrators believe
posture-based policy is in force when it is not.

  • Please also consider the interaction with #70181: if remote unconditionally wins when

present, and an empty/304 remote response zeroes out local managed rules when absent,
then endpoint-managed settings have no reliable configuration at all for an org that
uses the admin console. That combination is the blocking problem, more than either
issue alone.

Showing cached comments. Read the full discussion on GitHub ↗