You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Bash(cmd *) allowlist silently bypassed for commands interpolating user env vars; uninformative denial #49874
When Claude Code runs in --permission-mode dontAsk, Bash commands that interpolate non-standard environment variables (anything besides $HOME, $PWD, $PATH, $USER, etc.) are denied — even when the command prefix is allowlisted in settings.json.
The denial message is generic:
"Permission to use Bash has been denied because Claude Code is running in don't ask mode. [...]"
It's identical to a "no rule matched" response, so the model has no way to recognize the env-var reference as the cause and try a different form.
What Should Happen?
One of:
The command should be allowed, since the prefix (Bash(curl *)) matches.
Or, if env-var interpolation is deliberately restricted, the denial should say so explicitly — something like: "Blocked: command references non-standard env var $FOO. Add it to an allowed env-var list or rewrite the command."
The current behavior is the worst of both: restriction without a diagnostic signal.
Permission to use Bash has been denied because Claude Code is running in don't ask mode. IMPORTANT: You may attempt to accomplish this action using other tools that might naturally be used to accomplish this goal [...]
Standard vars ($HOME) pass; user-defined vars of any name are denied, including innocuous ones ($FOO, $MYVAR).
Claude Model
Sonnet (default)
Regression
Yes — worked on 2.1.111, broken on 2.1.112.
Our GitLab CI had a headless pipeline running the same curl --header "PRIVATE-TOKEN: $CLAUDE_GITLAB_TOKEN" "$CI_API_V4_URL/..." successfully on 2.1.111 (published MR, clean exit). After image rebuild to 2.1.112, same skill denied on same command, every run.
The 2.1.97 changelog mentions "tightening checks around env-var prefixes" — but that phrasing naturally reads as leading assignments (FOO=bar cmd), not interpolated references. If 2.1.112 extended the check to interpolated refs, it's undocumented.
Platform
Anthropic API
Operating System
Other Linux (Alpine 3.23 in Docker)
Terminal/Shell
Non-interactive/CI environment
Additional Information
Downstream failure modes we observed:
Fast bail-out (25 s). Model received one "don't ask mode" denial, interpreted it as "all Bash is blocked," cited the CLAUDE.md safety instruction ("exit non-zero if you cannot complete the workflow"), and exited cleanly. Pipeline succeeded by its own definition but did no work.
Subagent cascade (10 × Task delegations, ~$0.40 wasted). Same trigger, different day. Model saw the denial, spawned a Task subagent with an increasingly explicit prompt ("Bash IS available, just try it"), the subagent refused without invoking Bash at all, model spawned another subagent, etc. Seven consecutive retries, all rejecting on first principles. Classic learned-helplessness: one denial poisoned the fleet. Transcript (jsonl + 10 subagent jsonls) available if useful.
Our workaround:
We moved GitLab API calls behind a wrapper script (/usr/local/bin/gitlab-api) that resolves the env vars internally. Model calls gitlab-api GET merge_requests?state=opened — no env var in the command string, allowlist matches Bash(gitlab-api *). Works reliably, but shifts responsibility to every CI consumer to ship such a wrapper.
Suggestions (in priority order):
Informative denial message when the rule was matched but the env-var check rejected. Single biggest win — prevents the cascade.
Opt-out or namespace allowlist for CI/headless ("permissions.allowEnvVars": ["CI_*", "CLAUDE_GITLAB_TOKEN"], or Bash(curl * $CI_*) syntax). The rule is defensible for interactive use; CI environments intentionally expose env vars as the only interface for tokens/URLs.
Preflight
What's Wrong?
When Claude Code runs in
--permission-mode dontAsk, Bash commands that interpolate non-standard environment variables (anything besides$HOME,$PWD,$PATH,$USER, etc.) are denied — even when the command prefix is allowlisted insettings.json.The denial message is generic:
It's identical to a "no rule matched" response, so the model has no way to recognize the env-var reference as the cause and try a different form.
What Should Happen?
One of:
Bash(curl *)) matches.$FOO. Add it to an allowed env-var list or rewrite the command."The current behavior is the worst of both: restriction without a diagnostic signal.
Error Messages/Logs
Response text returned to the model (verbatim):
Steps to Reproduce
In a container with
settings.json:{"permissions": {"allow": ["Bash(curl *)"]}}Run:
claude -p "<prompt>" \ --permission-mode dontAsk \ --settings /path/to/settings.json \ --max-turns 3 \ --output-format jsonFor each prompt below, the reported
permission_denialsfield reveals the rule:curl -s https://httpbin.org/getcurl -s --header "X-Thing: $HOME" https://httpbin.org/headerscurl -s --header "X-Thing: $FOO" https://httpbin.org/headers(FOO=xyzin env)curl -s --header "X-Thing: $MYVAR" https://httpbin.org/headerscurl -s https://httpbin.org/anything/$CI_PROJECT_IDStandard vars (
$HOME) pass; user-defined vars of any name are denied, including innocuous ones ($FOO,$MYVAR).Claude Model
Sonnet (default)
Regression
Yes — worked on 2.1.111, broken on 2.1.112.
Our GitLab CI had a headless pipeline running the same
curl --header "PRIVATE-TOKEN: $CLAUDE_GITLAB_TOKEN" "$CI_API_V4_URL/..."successfully on 2.1.111 (published MR, clean exit). After image rebuild to 2.1.112, same skill denied on same command, every run.The 2.1.97 changelog mentions "tightening checks around env-var prefixes" — but that phrasing naturally reads as leading assignments (
FOO=bar cmd), not interpolated references. If 2.1.112 extended the check to interpolated refs, it's undocumented.Platform
Anthropic API
Operating System
Other Linux (Alpine 3.23 in Docker)
Terminal/Shell
Non-interactive/CI environment
Additional Information
Downstream failure modes we observed:
Fast bail-out (25 s). Model received one "don't ask mode" denial, interpreted it as "all Bash is blocked," cited the CLAUDE.md safety instruction ("exit non-zero if you cannot complete the workflow"), and exited cleanly. Pipeline succeeded by its own definition but did no work.
Subagent cascade (10 × Task delegations, ~$0.40 wasted). Same trigger, different day. Model saw the denial, spawned a
Tasksubagent with an increasingly explicit prompt ("Bash IS available, just try it"), the subagent refused without invoking Bash at all, model spawned another subagent, etc. Seven consecutive retries, all rejecting on first principles. Classic learned-helplessness: one denial poisoned the fleet. Transcript (jsonl + 10 subagent jsonls) available if useful.Our workaround:
We moved GitLab API calls behind a wrapper script (
/usr/local/bin/gitlab-api) that resolves the env vars internally. Model callsgitlab-api GET merge_requests?state=opened— no env var in the command string, allowlist matchesBash(gitlab-api *). Works reliably, but shifts responsibility to every CI consumer to ship such a wrapper.Suggestions (in priority order):
"permissions.allowEnvVars": ["CI_*", "CLAUDE_GITLAB_TOKEN"], orBash(curl * $CI_*)syntax). The rule is defensible for interactive use; CI environments intentionally expose env vars as the only interface for tokens/URLs.