[Feature Request] Add setting to skip protected directory prompts when bypassPermissions is enabled

Status Closed — not planned
Maintainer reply None cached
Activity 11 comments · opened Mar 18, 2026 · closed May 1, 2026

Problem

v2.1.78 introduced prompts for writes to protected directories (.git/, .claude/, .vscode/, .idea/) even when bypassPermissions mode is enabled. While this is a reasonable security default, users who have explicitly opted into bypassPermissions mode have already accepted full trust — prompting them for every edit to these directories adds friction without security benefit.

This is especially painful in VS Code where each prompt requires a mouse click on a dialog, and typical sessions involve many edits to .claude/settings.json, .claude/rules/, .vscode/settings.json, and occasionally .git/config.

Attempted Workarounds (all failed)

  1. permissions.allow patterns (e.g. Edit(.claude/**)) — do not override the protected directory prompt
  2. PermissionRequest hook returning {"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": {"behavior": "allow"}}}does not fire in VS Code extension (see #13203, #12176)
  3. PreToolUse hook returning permissionDecision: "allow" — does not override protected directory checks (v2.1.77 change)
  4. No CLI flag or env var exists for this

Proposed Solution

Add a skipProtectedDirectoryPrompts setting (boolean, default false) that, only when bypassPermissions is already enabled, skips the protected directory write prompt:

{
  "permissions": {
    "defaultMode": "bypassPermissions"
  },
  "skipProtectedDirectoryPrompts": true
}

This preserves the security improvement for users in default or acceptEdits modes while respecting the explicit trust decision of bypassPermissions users.

Additional Context

  • The PermissionRequest hook not firing in VS Code (#13203) means there is currently no workaround for VS Code users
  • skipDangerousModePermissionPrompt already exists as precedent for skipping prompts that bypass-mode users find redundant
  • permissions.deny rules would still block even with this setting (deny always wins), so users retain granular control

Environment

  • Claude Code v2.1.78
  • VS Code extension
  • Windows 11
  • bypassPermissions mode enabled via ~/.claude/settings.json

View original on GitHub ↗

11 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/35646
  2. https://github.com/anthropics/claude-code/issues/35718
  3. https://github.com/anthropics/claude-code/issues/35626

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

voyagi · 5 months ago

Update: Working Workaround Found (VS Code Extension Patch)

While a proper setting is still the ideal solution, I found a working workaround by patching the VS Code extension directly.

What we tried (all failed)

As documented in the issue body, plus one additional finding:

  • PermissionRequest hook: Actually does fire in VS Code now (issue #13203 was fixed around v2.1.30), but protected directory prompts use a separate code path that doesn't go through the PermissionRequest hook event. So this approach fails silently.

What works: Patching extension.js

The protected directory check originates in the native CLI binary (claude.exe), which sends can_use_tool requests to the VS Code extension. The extension's requestToolPermission method forwards these to a webview dialog. The patch intercepts at requestToolPermission before the dialog is created.

The extension already has an auto-approve pattern for Chrome MCP tools. The patch adds an identical check for protected directory paths right after it:

// Added after existing Chrome MCP auto-approve, same {behavior:"allow"} pattern
if (j?.file_path && /[\/]\.(?:git|claude|vscode|idea)[\/]/.test(j.file_path))
  return {behavior: "allow", updatedInput: j};

Full patch script (~130 lines) with --check, --revert, and backup support: auto-detects the extension directory, finds the anchor point in minified code, creates a backup, and applies the patch.

Results

  • Protected directory edits go through silently in VS Code, same as pre-v2.1.78 behavior
  • No flash or dialog after VS Code restart
  • Other permission dialogs (non-protected-dir) are unaffected
  • permissions.deny rules still block as expected (the check never reaches the patch for denied tools)

Why a proper setting is still needed

  • The patch must be re-applied after every Claude Code extension update (minified code changes between versions, anchor string may shift)
  • Patching minified code is inherently fragile
  • This approach isn't accessible to most users

Not a duplicate

The suggested duplicates (#35646, #35718, #35626) report the same problem but none have a workaround. This issue specifically requests a skipProtectedDirectoryPrompts setting, which remains the right long-term fix. The working patch demonstrates that the feature is technically simple to implement at the extension level.

xlurie · 5 months ago

Thanks @voyagi — your workaround saved my day. I independently hit the same wall
(v2.1.78 broke our DevContainer workflow where Claude edits .claude/ files constantly)
and went through the exact same failed attempts: permissions.allow patterns,
PermissionRequest hook, PreToolUse hook — nothing worked.

Found your issue and applied the extension.js patch. Works perfectly.

Additional notes from my setup (VS Code DevContainer, Linux, v2.1.79):

  1. Anchor string is stable between v2.1.78 and v2.1.79

return{behavior:"allow",updatedInput:j};let N=await this.sendRequest
exists in both versions unchanged.

  1. I automated it via postStartCommand in devcontainer.json so the patch

re-applies on every container rebuild. Script exits gracefully (exit 0) if
extension isn't installed yet or anchor string changed.

  1. Confirmed the .claude/skills/ exemption bug: docs say .claude/commands/,

.claude/agents/, .claude/skills/ should be exempt from protected dir prompts,
but they all prompt in v2.1.79. Filed separately as a bug observation but
your patch covers it anyway.

  1. inspect() bug: getInitialPermissionMode() uses

vscode.workspace.getConfiguration("claudeCode").inspect("initialPermissionMode")
which only checks workspaceFolderValue, workspaceValue, and globalValue.
Machine-level settings (set via VS Code Settings UI or devcontainer.json
customizations.vscode.settings) are NOT in any of those scopes — they're in
defaultValue. Workaround: use .vscode/settings.json (workspace level)
instead of devcontainer.json settings.

+1 for skipProtectedDirectoryPrompts as a proper setting.

prodan-s · 5 months ago

+1 — this is a real problem for anyone with hook-based enforcement infrastructure.

I have ~36 hooks that fire on Write|Edit (format validation, integrity checks, quality gates). The protected directory prompt pushes toward using Bash writes as a workaround — which silently bypasses the entire PostToolUse hook layer. The "fix" is worse than the problem.

A skipProtectedDirectoryPrompts setting (gated on bypassPermissions already being enabled) would be the clean solution. The user has already opted into full trust by enabling bypass mode.

xlurie · 5 months ago

Update: v2.1.81 broke the patch — fixed with dynamic anchor detection

The anchor string changed in v2.1.81: let N=await became let U=await. The patch script silently skipped with "Anchor pattern not found" — so we were back to permission prompts.

Fix: replaced the hardcoded variable name with dynamic extraction via regex:

# Old (breaks on every minifier rename):
grep -qF '...;let N=await this.sendRequest' "$EXT_JS"

# New (survives variable renames):
ANCHOR_VAR=$(grep -oP 'return\{behavior:"allow",updatedInput:j\};let \K[A-Za-z_$]+(?==await this\.sendRequest)' "$EXT_JS" | head -1)

The variable name (N, U, or whatever comes next) is captured and passed to the Python patcher as a parameter. Everything else stays the same.

Anchor stability across versions:
| Version | Variable | Surrounding pattern |
|---------|----------|-------------------|
| 2.1.78 | N | return{behavior:"allow",updatedInput:j};let N=await this.sendRequest |
| 2.1.79 | N | same |
| 2.1.81 | U | same (only variable name changed) |

The surrounding pattern (return{behavior:"allow",updatedInput:j};let ...=await this.sendRequest) has been stable across all three versions so far. The dynamic approach should survive future updates as long as this pattern holds.

Still running via postStartCommand in DevContainer — re-applies automatically on every rebuild.

lightrow · 5 months ago

this seems to also affect ~/.claude directory as well. I store certain things in there that are updated headless by agents and this no longer works, had to downgrade to v2.1.77 and disable auto updates for now. Symlinking could be another alternative, but a skipProtectedDirectoryPrompts option would be welcomed

xlurie · 5 months ago

Update: Patch v2 — Bash tool coverage (v2.1.83)

The original patch only checks j?.file_path, which covers Write/Edit tools. Bash tool commands (mkdir, touch) in protected directories also prompt — but Bash uses j?.command, not j?.file_path, so the patch missed them.

Testing matrix (v2.1.83, bypassPermissions, VS Code DevContainer):

| Command | Type | Prompts? |
|---------|------|:--------:|
| ls .claude/ | read | No |
| cat .claude/... | read | No |
| find .claude/ | read | No |
| stat .claude/... | read | No |
| mkdir -p .claude/... | write | Yes |
| touch .claude/... | write | Yes |
| cp ... .claude/... | write | Yes |
| echo > .claude/... | write | Yes |
| rm -rf .claude/... | write | Yes |

Read-only commands pass silently. All write commands prompt.

Patch v2 adds a second check for safe Bash commands:

// Existing: Write/Edit tools (file_path check)
if(j?.file_path && /[\/\\]\.(?:git|claude|vscode|idea)[\/\\]/.test(j.file_path))
  return {behavior:"allow", updatedInput:j};

// NEW: Bash tool — only mkdir and touch (command check)
if(j?.command && /^(mkdir|touch)\b/.test(j.command.trim())
   && /[\/\\]\.(?:git|claude|vscode|idea)[\/\\]/.test(j.command))
  return {behavior:"allow", updatedInput:j};

Destructive commands (cp, rm, mv, sed -i, echo >) still prompt — by design.

Anchor stability update: pattern return{behavior:"allow",updatedInput:j};let <VAR>=await this.sendRequest holds in v2.1.83 (var=v). Dynamic extraction continues to work.

| Version | Variable |
|---------|----------|
| 2.1.78 | N |
| 2.1.79 | N |
| 2.1.81 | U |
| 2.1.83 | v |

xlurie · 5 months ago

v2.1.84 update: Minifier renamed variables — jB for updatedInput, let variable also changed. Fixed the patch script to dynamically grep both variable names from the anchor pattern instead of hardcoding them. Re-tested, works on 2.1.84.

yurukusa · 5 months ago

You can auto-approve protected directory writes with a hook, giving you fine-grained control over which directories are exempt:

INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -z "$FILE" ] && exit 0
case "$FILE" in
    */.claude/commands/*|*/.claude/agents/*|*/.claude/skills/*|*/.claude/memory/*)
        jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"Protected directory write auto-approved"}}'
        ;;
    */.vscode/settings.json|*/.vscode/launch.json|*/.vscode/tasks.json)
        jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:".vscode config write auto-approved"}}'
        ;;
esac
exit 0

Same hook for Edit:

{
  "hooks": {
    "PreToolUse": [
      {"matcher": "Write", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/protected-dir-allow.sh"}]},
      {"matcher": "Edit", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/protected-dir-allow.sh"}]}
    ]
  }
}

This lets you selectively exempt directories from the protected-directory prompt while keeping .git/ and other sensitive directories gated. Adjust the case patterns to match your workflow.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 3 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.