settings.json `env` block overrides `env(1)` prefix for CLAUDE_CODE_SUBPROCESS_ENV_SCRUB, breaking the documented per-invocation opt-out and forcing --permission-mode to default
CLI version: v2.1.207 (also reproduced on v2.1.201 for the permission-mode symptom)
Summary
When ~/.claude/settings.json sets "env": {"CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1"}, that value wins over an env(1)-prefix override on the spawned claude process (e.g. env CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 claude ...) — the subprocess still reads 1. This holds both inside a PreToolUse hook's own subprocess and inside a real Bash-tool-invoked subprocess. The same override mechanism works correctly for arbitrary (non-CLAUDE_CODE_*) environment variables, so the issue is specific to this reserved key, not a general env(1)-vs-settings-env precedence rule.
This has two consequences:
- There is no working per-invocation way to opt a single launched session out of the subprocess-env scrub, even though the CLI's own runtime message (quoted below) explicitly instructs the user to do exactly that.
- The same scrub-forced-on state independently forces
--permission-modetodefault(the most restrictive, "Manual," mode) regardless of the value passed to--permission-mode(auto,dontAsk,bypassPermissionsall tested, all forced todefault).
Reproduction (item 1 — env precedence)
# Given ~/.claude/settings.json contains:
# { "env": { "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1" } }
env CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 claude -p "run: env | grep CLAUDE_CODE_SUBPROCESS_ENV_SCRUB" \
--allowedTools "Bash(env*)"
# Expected: output shows CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0
# Actual: output shows CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1
Also confirmed identically from inside a PreToolUse hook's own subprocess (a hook script that does env | grep CLAUDE_CODE_SUBPROCESS_ENV_SCRUB and writes it to a file), invoked via the same env(1)-prefixed claude -p call.
Control test proving the anomaly is key-specific, not general precedence:
env TEST_MARKER=x claude -p "run: env | grep TEST_MARKER" --allowedTools "Bash(env*)"
# Result: TEST_MARKER=x — propagates correctly.
Same invocation shape, same subprocess contexts (hook subprocess and real Bash-tool subprocess) — only CLAUDE_CODE_SUBPROCESS_ENV_SCRUB fails to propagate; an arbitrary variable passed the identical way succeeds.
Reproduction (item 2 — permission-mode forcing)
With CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 present (whether via settings.json or inherited), starting a session with any of:
claude --permission-mode auto ...
claude --permission-mode dontAsk ...
claude --permission-mode bypassPermissions ...
...all boot into default (Manual) mode instead. The CLI itself prints, at startup:
Permission mode forced to default — CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set (allowed_non_write_users hardening). Declare allowedTools explicitly, or set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 to opt out.
That message names the documented escape hatch (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0) — but per item 1 above, setting it via env(1) on the spawned process does not actually take effect when settings.json's env block also sets =1. So the escape hatch the CLI itself advertises does not work in this configuration. --allowedTools declared explicitly (both bare tool names and Tool(*) syntax) was also tried and did not clear the forced mode.
Expected behavior
One of:
- A spawned process's own
env(1)-level (or otherwise explicitly-passed) value forCLAUDE_CODE_SUBPROCESS_ENV_SCRUBshould take precedence oversettings.json'senvblock, consistent with how ordinary environment variables behave and consistent with the CLI's own advertised opt-out message, OR - If this variable is intentionally reserved/pinned once set in
settings.json(similar to howCLAUDE_CODE_REMOTE/CLAUDE_CODE_ACCOUNT_UUIDare documented as "ignored when set in the env block"), the CLI should not print a runtime message telling the user to set it per-invocation, since doing so cannot work — and the actual supported opt-out mechanism (if one exists) should be documented atcode.claude.com/docs/en/settingsandcode.claude.com/docs/en/permission-modes. Neither page currently mentionsCLAUDE_CODE_SUBPROCESS_ENV_SCRUBor the "allowed_non_write_users hardening" concept at all.
Impact
Low severity from a security-exposure standpoint (we independently confirmed via a presence-only env | wc -l count that the stuck-on scrub produces zero differential subprocess-env exposure between a normal session and a session that attempted the opt-out — i.e. this is a correctness/usability bug, not a live secrets leak). But it does mean: (a) the documented-in-product opt-out is dead, and (b) any automation that expects --permission-mode to take effect when this variable is present in the environment (e.g. a launcher script that wants to hand off to an unattended/auto-approved session) silently gets forced back to the most restrictive mode with no error, only a printed notice that names a remedy that doesn't work.
What we'd like
- Confirmation of whether this is intended (reserved-key pinning) or a bug.
- If intended, updated public docs describing the actual supported way (if any) to opt a single invocation out, and a corrected runtime message.
- If a bug, a fix restoring env(1)-level override precedence for this key, consistent with how other environment variables behave.
Happy to provide the full probe transcript / isolated reproduction harness on request.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗