Allow configuring protected directories in bypass permissions mode

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

Problem

When using bypassPermissions mode (or --dangerously-skip-permissions), writes to .claude/, .git/, .vscode/, and .idea/ directories still prompt for confirmation. While the intent is safety, this creates friction in two scenarios:

1. Autonomous agent workflows (NightShift / Ralph loops)

Workers running in bypass mode need to write review/communication files to .claude/nightshift/. Every write triggers a permission prompt, breaking autonomous execution. The current exemptions (.claude/commands/, .claude/agents/, .claude/skills/) don't cover this.

2. Power user sessions with heavy .claude/rules/ editing

Users who frequently edit .claude/CLAUDE.md or .claude/rules/*.md get prompted repeatedly despite explicitly opting into bypass mode. The user already accepted the risk by enabling bypass — re-prompting for every .claude/ write undermines that choice.

Proposed Solution

Add a configurable setting to extend the exemption list:

{
  "permissions": {
    "defaultMode": "bypassPermissions",
    "bypassProtectedPaths": [
      ".claude/"
    ]
  }
}

Or alternatively, a broader toggle:

{
  "skipProtectedDirectoryPrompts": true
}

Alternatives considered

  • Moving files out of .claude/ — works but forces architectural compromises to work around a CLI limitation
  • Using option 2 ("allow all edits in dir") — only lasts per-session, resets on restart, and doesn't help headless/autonomous workers
  • skipDangerousModePermissionPrompt: true — already set, but this only skips the initial bypass mode confirmation, not the per-directory protections

Context

  • Claude Code v2.1.81
  • Platform: Windows 11 (Git Bash)
  • Use case: Monorepo with extensive .claude/ configuration (rules, skills, nightshift state, hooks)
  • The .claude/skills/ exemption already proves this is safe to configure — extending it to other .claude/ subdirs follows the same pattern

Who benefits

  • Anyone using bypassPermissions mode who edits .claude/ files frequently
  • Autonomous agent frameworks (NightShift, Ralph loops, CI pipelines) that need to write state files
  • Teams with custom .claude/ directory structures beyond commands/agents/skills

🤖 Generated with Claude Code

View original on GitHub ↗

11 Comments

yurukusa · 5 months ago

A PreToolUse hook can selectively unlock specific protected directories while keeping others locked:
\\\bash
INPUT=\$(cat)
TOOL=\$(echo "\$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
FILE=\$(echo "\$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[[ "\$TOOL" != "Edit" && "\$TOOL" != "Write" ]] && exit 0
[[ -z "\$FILE" ]] && exit 0
UNLOCKED=(
".claude/commands"
".claude/skills"
".claude/agents"
".claude/rules"
".claude/todo.md"
)
for pattern in "\${UNLOCKED[@]}"; do
if [[ "\$FILE" == *"\$pattern"* ]]; then
jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"unlocked protected directory"}}'
exit 0
fi
done
exit 0
\
\\
This gives you per-path control: unlock \.claude/commands/\ for agent workflows while keeping \.claude/hooks/\ and \.claude/settings.json\ locked.
For \.git/\ — you almost never want to unlock that. For \.vscode/\/\.idea/\ — those are safer to unlock entirely since they're IDE config, not security-critical.

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/35942
  2. https://github.com/anthropics/claude-code/issues/36044
  3. https://github.com/anthropics/claude-code/issues/36282

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

lightrow · 5 months ago
A PreToolUse hook can selectively unlock specific protected directories while keeping others locked: ``bash INPUT=$(cat) TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null) [[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]] && exit 0 [[ -z "$FILE" ]] && exit 0 UNLOCKED=( ".claude/commands" ".claude/skills" ".claude/agents" ".claude/rules" ".claude/todo.md" ) for pattern in "${UNLOCKED[@]}"; do if [[ "$FILE" == _"$pattern"_ ]]; then jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"unlocked protected directory"}}' exit 0 fi done exit 0 ` This gives you per-path control: unlock .claude/commands/ for agent workflows while keeping .claude/hooks/ and .claude/settings.json locked. For .git/ — you almost never want to unlock that. For .vscode//.idea/` — those are safer to unlock entirely since they're IDE config, not security-critical.

It doesn't seem to work.

floraldo · 5 months ago
> A PreToolUse hook can selectively unlock specific protected directories while keeping others locked: bash INPUT=$(cat) TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null) [[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]] && exit 0 [[ -z "$FILE" ]] && exit 0 UNLOCKED=( ".claude/commands" ".claude/skills" ".claude/agents" ".claude/rules" ".claude/todo.md" ) for pattern in "${UNLOCKED[@]}"; do if [[ "$FILE" == _"$pattern"_ ]]; then jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"unlocked protected directory"}}' exit 0 fi done exit 0 This gives you per-path control: unlock .claude/commands/ for agent workflows while keeping .claude/hooks/ and .claude/settings.json locked. For .git/ — you almost never want to unlock that. For .vscode//.idea/ — those are safer to unlock entirely since they're IDE config, not security-critical. It doesn't seem to work.

Same.

I tried to implement the solution but it still prompts for permission

floraldo · 5 months ago

Update: PreToolUse hook does NOT work — PermissionRequest hook does

After implementing the PreToolUse hook suggested above, I can confirm it does not suppress the protected directory prompts. @lightrow reported the same.

The reason (clarified by @yurukusa in #37496): PreToolUse runs _before_ the built-in protected-directory checks, so its permissionDecision: "allow" gets overridden downstream. PermissionRequest runs _after_ those checks, which is why it sticks.

Working workaround (confirmed across #36044, #36282, #37496):

  1. Set bypassPermissions at user level (~/.claude/settings.json)
  2. Add a scoped PermissionRequest hook that only auto-approves protected directory writes:
{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/allow-protected-dirs.sh"
          }
        ]
      }
    ]
  }
}

~/.claude/hooks/allow-protected-dirs.sh:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_DIRS=(".claude/" ".git/" ".vscode/" ".idea/")
for dir in "${PROTECTED_DIRS[@]}"; do
    if [[ "$FILE_PATH" == *"$dir"* ]]; then
        jq -n '{
            hookSpecificOutput: {
                hookEventName: "PermissionRequest",
                decision: { behavior: "allow" }
            }
        }'
        exit 0
    fi
done
exit 0

This is scoped — only auto-approves the 4 protected directories, plan approvals and other prompts still work normally.

Related issues with the same problem: #35942, #36044, #36282, #37496

The core ask remains: Anthropic should add a skipProtectedDirectoryPrompts setting so users don't need hook workarounds for behavior they already opted into via bypassPermissions.

yurukusa · 5 months ago

Just shipped a PermissionRequest example hook for this exact scenario in cc-safe-setup v28.6.0:

npx cc-safe-setup --install-example allow-git-hooks-dir

@floraldo's analysis was spot on — the execution order is:

  1. PreToolUse hooks → 2. Built-in protected-directory checks → 3. PermissionRequest hooks

So permissionDecision: "allow" in PreToolUse gets overridden by step 2. PermissionRequest is the correct hook type for bypassing protected directory prompts.
Added this to our Troubleshooting docs as well.

interconnectedMe · 5 months ago

Yes please. I run extensive hook scripts and agent workflows that frequently touch .claude/hooks/ and .claude/skills/. The blanket protected directory block with no override is incredibly frustrating — I chose bypass mode for a reason. At minimum, let us configure which subdirectories under .claude/ should be exempt. This would solve 90% of the pain.

yurukusa · 5 months ago

You can auto-approve writes to specific .claude/ subdirectories with a hook:

INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -z "$FILE" ] && exit 0
case "$FILE" in
    */.claude/nightshift/*|*/.claude/reviews/*|*/.claude/logs/*)
        jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"Writes to custom .claude/ subdirectory auto-approved"}}'
        ;;
    */.claude/commands/*|*/.claude/agents/*|*/.claude/skills/*)
        jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"Writes to standard .claude/ subdirectory auto-approved"}}'
        ;;
esac
exit 0

Same for Edit:

INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
[ -z "$FILE" ] && exit 0
case "$FILE" in
    */.claude/nightshift/*|*/.claude/reviews/*|*/.claude/logs/*)
        jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow",permissionDecisionReason:"Custom .claude/ directory edit auto-approved"}}'
        ;;
esac
exit 0
{
  "hooks": {
    "PreToolUse": [
      {"matcher": "Write", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/claude-dir-allow.sh"}]},
      {"matcher": "Edit", "hooks": [{"type": "command", "command": "bash ~/.claude/hooks/claude-dir-edit-allow.sh"}]}
    ]
  }
}

This gives your NightShift workers write access to .claude/nightshift/ without prompts, while .git/ and other protected directories remain gated.

floraldo · 5 months ago

Update: Windows backslash fix for the PermissionRequest hook

The PermissionRequest hook approach from my earlier comment has been working reliably — but we hit a subtle issue on Windows where it would auto-approve some .claude/ paths but not others.

Root cause: On Windows, Claude sends file_path with backslashes (kinsai\clients\ideawise\.claude\skills\...), but the bash hook checks for forward-slash patterns (.claude/). Paths directly under the repo root (.claude/settings.json) matched because the path still contained /.claude/ from the repo prefix, but deeply nested .claude/ directories (we have 30+ across our monorepo) failed the match.

Fix — normalize backslashes before matching:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Normalize backslashes to forward slashes (Windows paths)
NORMALIZED="${FILE_PATH//\//}"

PROTECTED_DIRS=(".claude/" ".git/" ".vscode/" ".idea/")
for dir in "${PROTECTED_DIRS[@]}"; do
    if [[ "$NORMALIZED" == *"$dir"* ]]; then
        jq -n '{
            hookSpecificOutput: {
                hookEventName: "PermissionRequest",
                decision: { behavior: "allow" }
            }
        }'
        exit 0
    fi
done
exit 0

One-line diff from the original: NORMALIZED="${FILE_PATH//\//}" + match against $NORMALIZED instead of $FILE_PATH.

Relevant for anyone running Claude Code on Windows (Git Bash / MSYS2). macOS/Linux users are unaffected since paths already use forward slashes.

The core ask still stands — a native skipProtectedDirectoryPrompts setting would eliminate this entire class of cross-platform edge cases.

github-actions[bot] · 3 months ago

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

github-actions[bot] · 2 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.