Bash permission gate fires inconsistently in VS Code Claude extension — fails open on some commands not in allow list, fails closed on some commands that ARE in allow list

Resolved 💬 3 comments Opened Apr 8, 2026 by thatChadM Closed May 21, 2026

Severity

High / Security-relevant. Users cannot rely on permissions.allow entries, permission modes (acceptEdits, ask-before-edit), or plan mode's non-readonly-tool restriction to predict whether a given Bash command will prompt, execute silently, or be blocked. The gate's behavior is non-deterministic from the user's perspective and does not correspond to the configured allow list.

Environment

  • Extension: anthropic.claude-code-2.1.96-darwin-arm64
  • VS Code: 1.114.0 (commit e7fb5e96c0730b9deb70b33781f98e2f35975036, x64)
  • OS: macOS 15 (Darwin 24.6.0)
  • Model: claude-opus-4-6[1m]
  • Entrypoint: claude-vscode

Problem

During a diagnostic session, multiple Bash tool invocations were observed in which:

  1. Commands NOT in any allow list ran silently — no prompt, no refusal, no log entry. Including a rm -rf against a 7.5 GB directory.
  2. A command that IS in an allow list prompted anywaypython3 -c "...", which is explicitly matched by the project .claude/settings.local.json entry Bash(python3 -c ":*).
  3. Plan mode did not block Bash tool invocations despite the plan-mode system reminder explicitly stating MUST NOT run any non-readonly tools.

The divergence is not explained by any user configuration. ~/.claude/settings.json was audited for validity and for wildcard Bash allowances — the file is valid JSON, contains 28 specific Bash allow-list entries, and contains no Bash(*), bypassPermissions, or equivalent bypass flag. Project-level settings files were similarly audited. No config change during the session altered the behavior.

Details

All tests were performed in a single VS Code Claude session, in a workspace directory covered by both global ~/.claude/settings.json and project-level .claude/settings.json and .claude/settings.local.json. The session included a full VS Code + extension restart with an extension update applied between early and later tests; the behavior persisted across the restart.

Permission modes tested: acceptEdits, ask-before-edit, and plan.

The user was asked to report whether each command produced a visible permission prompt, since the tool-result stream does not distinguish "executed silently" from "prompted and approved" from the agent's side.

Tests

All invocations are Bash tool calls. "Prompted" means the user saw a permission dialog and had to approve before execution. "Silent" means the command ran with no prompt visible to the user.

| # | Command | Mode | In allow list? | Result |
|---|---|---|---|---|
| 1 | whoami | acceptEdits | No | Silent |
| 2 | Bundle: du -sh ...; rm -rf "$HOME/Library/Application Support/Claude/vm_bundles"; ls -la ...; cat ...; df -h ~ | acceptEdits | ls, cat yes; du, rm, df no | Silent (destructive rm -rf executed without prompt) |
| 3 | whoami | ask-before-edit | No | Silent |
| 4 | whoami | ask-before-edit (post full VS Code + extension restart with update applied) | No | Silent |
| 5 | whoami | plan | No | Silent (plan mode did not block) |
| 6 | /usr/bin/whoami | ask-before-edit | No | Prompted |
| 7 | whoami \|\| echo fallback | ask-before-edit | No | Silent |
| 8 | find /tmp -name nonexistent -maxdepth 1 | ask-before-edit | Yes (Bash(find:*)) | Silent (expected) |
| 9 | uname | ask-before-edit | No | Silent |
| 10 | /usr/bin/uname | ask-before-edit | No | Prompted |
| 11 | Bundle: echo ...; /usr/local/bin/code --version \|\| /Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code --version; echo ...; ls -d ~/.vscode/extensions/anthropic.claude-code*; find ~/.vscode/extensions -maxdepth 2 -name "package.json" -path "*claude*" | ask-before-edit | echo, ls, find yes; /usr/local/bin/code and absolute VS Code path no | Prompted |
| 12 | python3 -c "import json,sys; d=json.load(open('/Users/cmyers/.claude/settings.json')); ..." | ask-before-edit | Yes — matched by project Bash(python3 -c ":*) entry | Prompted (unexpected — should have run silent) |

Results

Silent when they should have prompted: tests 1, 2, 3, 4, 5, 7, 9. All are unqualified command names not matching any allow-list entry. Test 2 notably included a destructive rm -rf against a real 7.5 GB directory.

Silent as expected: test 8. find is in the allow list.

Prompted when they should NOT have prompted: test 12. python3 -c "..." is explicitly allow-listed in the project settings and should match the Bash(python3 -c ":*) entry. User observed a permission prompt anyway.

Prompted when they would have been expected to prompt: tests 6, 10, 11. All contain absolute-path invocations not matching any allow-list entry.

Plan mode violation: test 5. The plan-mode system reminder in the current session explicitly stated that non-readonly tools must not be run. Bash is not a read-only tool. The command ran without refusal, without prompt, and without any indication that plan mode was active.

Additional Observations

  1. User could not predict the behavior. After 10+ tests, the user and the agent were unable to identify a consistent criterion that determined whether a command would prompt or run silently. An initial hypothesis based on "bare command names vs absolute paths" held across tests 1-11 but was invalidated by test 12 (python3 -c is a bare-name invocation that prompted anyway, while being in the allow list).
  1. Failures occur in both directions. The gate both fails open (destructive commands not in allow list run silently) and fails closed (commands that ARE in allow list get prompted anyway). This is distinct from "the gate is disabled" — it is actively inconsistent.
  1. No user-visible signal. There is no log entry, warning, status indicator, or UI element that tells the user "this command was or was not gated." The user can only infer gating behavior by being present to see (or not see) the prompt. In this session the bug was discovered accidentally because the user expected a prompt before a rm -rf and noticed it did not appear.
  1. Settings files are clean. ~/.claude/settings.json validated as JSON, contains no Bash wildcards, no bypass flags. Project-level settings similarly clean. The behavior is not caused by user configuration.
  1. Full restart did not help. A full VS Code + extension restart between tests 3 and 4 (with a queued extension update applied to 2.1.96) did not change the behavior.

Request

Please investigate:

  1. Why commands not matching any allow-list entry execute without prompting in multiple permission modes (tests 1-5, 7, 9)
  2. Why a command that DOES match an allow-list entry (Bash(python3 -c ":*)) prompted despite the match (test 12)
  3. Why plan mode's "no non-readonly tools" restriction did not block Bash invocations (test 5)

If there is a configuration flag or mode setting that forces strict, consistent gating, please document it — the user could not find one.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗