[BUG] Empty server-managed settings (304 cached) zero out local managed-settings.json — deny/allow rules never enforced
Platform: Linux (RHEL 9)
Claude Code version: current (tested 2026-06-22)
Problem
When a facility deploys /etc/claude-code/managed-settings.json with permissions.allow and permissions.deny rules, those rules are silently discarded at startup. All policySettings destinations end up with 0 rules regardless of what the file contains.
Root cause (from debug log)
The documented precedence is server-managed > managed-settings.json. When the deployment has no server-managed policy configured with Anthropic, the server returns an empty/cached 304 response. Claude Code treats this as "server-managed policy = zero rules" and replaces policySettings with empty arrays, overwriting the local file.
\\\\
[DEBUG] MDM settings load completed in 2ms # local file read
[DEBUG] Remote managed settings loaded # empty 304 from Anthropic
[DEBUG] Replacing all allow rules for destination 'policySettings' with 0 rule(s): []
[DEBUG] Replacing all deny rules for destination 'policySettings' with 0 rule(s): []
\\
The local file is valid and readable (managed-settings.json is world-readable, no symlink issues).
Expected behavior
An empty/absent server-managed response should be treated as "not configured" and fall through to managed-settings.json. It should not be treated as a configured policy of zero rules that overrides the local file.
Actual behavior
All policySettings allow/deny rules are zeroed out. Any rules in managed-settings.json — including deny rules meant to restrict tool access — are never enforced.
Impact
Facility-wide deny rules (e.g., Bash(condor_*)) are never enforced on shared systems. Users can run any command without restriction, defeating the purpose of managed settings for shared multi-user environments.
Reproduction
- Deploy
/etc/claude-code/managed-settings.jsonwith deny rules on a Linux system with no Anthropic enterprise/server-managed policy configured - Run
claude --debug-file /tmp/debug.log -p hello - Grep for
policySettingsin the debug log — all rules show0 rule(s): []despite the valid local file
Example managed-settings.json
{
"permissions": {
"allow": ["Bash(pixi*)", "Bash(du*)", "Bash(df*)"],
"deny": ["Bash(condor_*)"]
}
}
Notes
- The same behavior occurs with
managed-settings.d/drop-in files (tested prior to switching tomanaged-settings.json) - The debug log confirms the file is being watched:
Watching for changes in setting files ... /etc/claude-code/managed-settings.json... and drop-in directory /etc/claude-code/managed-settings.d - The remote settings fetch returns 304 (cached), suggesting this affects any deployment without a paid Anthropic enterprise MDM policy
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
This is a nasty one because it's a silent failure of a
denyrule — the operator believes a facility-wide restriction is in force, and it simply isn't. On a shared multi-user box that's a real security gap, not just a config annoyance, so thank you for the precise root-cause and the debug trace. The "empty/304 server-managed response should mean not configured, fall through to local" expectation looks right to me, andReplacing all deny rules ... with 0 rule(s): []overwriting a valid local file is the bug.While that's triaged, here's a mitigation that doesn't depend on the broken
policySettingspath at all, so yourBash(condor_*)deny can be enforced again today.A
PreToolUsehook runs before the tool executes and is evaluated independently of the allow/deny permission merge. So even whenpolicySettingshas been zeroed, a hook can hard-block the command deterministically:Wired in
managed-settings.json:Two honest caveats, because I don't want to hand you a false sense of safety:
hooksdestination (your trace only showspolicySettings). Please run yourclaude --debug-filerepro and grep for the hook loading /PreToolUselines to confirm the hook is actually registered. Ifhookssurvives the merge, the snippet above closes the gap; if it gets zeroed too, that's a second instance of the same root cause and worth noting in this issue.condor_*" is the OS layer (the scheduler's own auth, a wrapper, orsudo/PAM rules). Treat the hook as defense-in-depth, not the sole control, on shared systems.If it helps, the free
cc-safe-setuprepo has apermission-denial-enforcerhook that implements exactly this independent-enforcement pattern (and the pinning/precedence edge cases around it) — reference implementation, nothing to buy: https://github.com/yurukusa/cc-safe-setupTo be clear on what's first-hand vs not: the 304/precedence root cause is your analysis (which reads correctly to me); the
PreToolUse-runs-regardless-of-policySettings behavior and theexit 2block are things I've used and can vouch for. The caveat about whetherhooksis also zeroed is exactly the part I haven't tested on your build.Possibly the same root-cause family: #72634 shows the
policyHelperrung of the same documented managed-tier ladder also losing to the remote payload — in that case the helper is documented to preempt server-managed settings entirely, yet on macOS/v2.1.205 it executes and its well-formed output is silently discarded (canary-verified reproduction in that issue's comments). Both behaviors are consistent with the remote cache unconditionally winning whenever it exists, regardless of the documented precedence.I was hoping to use managed settings as a way to enforce tool constraints in a sandbox environment, so this bug is problematic for me as well. My use case is I want to prevent claude from making all web requests except those that are explicitly allowed assuming an adversarial claude (either through misalignment or through prompt injection). Simply setting up a proxy that it must connect to isn't enough, because the web search and fetch tools are run on Anthropic's side, not in my process. Since I must have the Anthropic API exposed for claude to work, it can make any web request it wants even if I set up a local proxy. I _was_ planning to use managed permissions to prevent those tools from being run, but it seems they are ignored. I do not know if
--dangerously-skip-permissionsis relevant here, but I do want to run with that flag. The sandboxed environment that claude runs in is what will make it safe, but I don't want it asking the user for approvals. I can easily configure the managed settings to enforce immutability, but the same is not true of the user settings which lives in claude's user directory which it needs to edit for benign use cases like authentication.