[FEATURE] PreToolUse hooks should override permission grants for defense-in-depth security

Resolved 💬 3 comments Opened Jan 30, 2026 by tbrianjones Closed Feb 3, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Summary

When implementing defense-in-depth security using PreToolUse hooks, users need certain high-risk commands (like git push, git merge, rm -rf) to always require explicit confirmation, even when broad Bash permissions are granted. Currently, hooks cannot override existing permission grants, making defense-in-depth impossible.

The Dilemma

Users face a forced choice between productivity and security:

  1. Grant broad Bash permissions → Fast development, but hooks' permissionDecision: "ask" or "deny" are ignored for allowed tools
  2. 2. Keep Bash restricted → Hooks work, but constant permission prompts for every piped/chained command during development

Real-World Use Case

I implemented a PreToolUse hook (security-check.sh) that:

  • Detects dangerous commands: git push, git merge, git rebase, rm -rf
  • - Returns {"permissionDecision": "ask", "reason": "Destructive git operation requires confirmation"}

Expected: Hook prompts user for confirmation before these specific commands
Actual: Hook is completely bypassed because Bash is in the allow list

Related Issues (This is a Longstanding Problem)

This issue has been reported multiple times and affects many users:

  • #4669 - permissionDecision: "deny" in PreToolUse hooks is ignored (closed due to inactivity, not resolved)
  • - #6527 - ask list is ignored when "Bash" is in allow list
  • - - #13214 - PreToolUse hooks bypassed when permission rule matches
  • - - - #13339 - VS Code Extension ignores Hook permissionDecision: "ask"
  • - - - - #13744, #13756 - PreToolUse hooks with exit code 2 don't block tool execution
  • - - - - - #15237 - ask permission rules don't take precedence over allow
  • - - - - - - #18312 - PreToolUse hook permissionDecision ignored when tool in allow list
  • - - - - - - - #12690 - Allow list overrides deny list (possible destructive behavior)

User @tomomonta's testing in #4669 confirmed ALL blocking mechanisms fail:

  1. permissionDecision: "deny" - Tool executes anyway
  2. 2. permissionDecision: "ask" - No user prompt shown, tool executes
  3. 3. "continue": false - Tool execution continues regardless
  4. 4. Non-zero exit codes - Tool proceeds even with exit code 1 or 2

Proposed Solution

Core Request: Hooks Should Always Run First

PreToolUse hooks should execute before permission evaluation, and their decisions should be respected regardless of existing permission grants.

Proposed Implementation

Option A: Hook Priority Flag (Preferred)

Add a priority: "override" option to hook configuration:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "priority": "override",  // Hook runs and its decision is respected even if tool is allowed
      "hooks": [{"type": "command", "command": "./security-check.sh"}]
    }]
  }
}

Option B: New Permission Decision Value

Add "force-ask" and "force-deny" that cannot be overridden:

{"permissionDecision": "force-ask", "reason": "Destructive operation requires confirmation"}

Option C: Fix Current Behavior

Make the documented behavior actually work - hooks should run before permission checks as the documentation states:

"When Claude Code makes a tool call, PreToolUse hooks run before the permission system runs, and the hook output can determine whether to approve or deny the tool call in place of the permission system."

Expected User Experience

  1. User grants broad Bash permission for productivity
  2. 2. User configures hook to intercept dangerous commands
  3. 3. Normal commands execute without prompts
  4. 4. Dangerous commands (git push, rm -rf, etc.) trigger user confirmation via the hook
  5. 5. User confirms or denies the specific operation

This enables defense-in-depth: multiple independent security layers where hooks act as a final safety check regardless of permission settings.

Alternative Solutions

Current Workarounds (All Inadequate)

1. Hard Block via Exit Code 2

  • What it does: Completely blocks the command
  • - Problem: Can't allow the operation after user confirmation - it's all-or-nothing
  • - - Use case broken: Can't use Claude for legitimate git push operations

2. Remove Bash from Allow List

  • What it does: Hooks work as intended
  • - Problem: Constant permission prompts for every piped command, chained command, and multi-command during development
  • - - Use case broken: Development velocity destroyed by prompt fatigue

3. Manual Command Execution

  • What it does: Claude tells user to run dangerous commands manually
  • - Problem: Breaks the agentic workflow entirely
  • - - Use case broken: Defeats the purpose of AI-assisted development

4. Wrapper Scripts with Aliases

  • What it does: User creates gpush alias to run git push outside Claude
  • - Problem: Doesn't integrate with Claude's workflow or permission system
  • - - Use case broken: Manual intervention required for every deployment

Why This Matters

Enterprise and security-conscious users want to use Claude Code but need guardrails. The current permission system forces an all-or-nothing choice that doesn't reflect how security actually works in practice.

Defense-in-depth is a fundamental security principle: multiple independent layers of protection. Hooks were designed to be one of those layers, but they're currently ineffective when combined with practical permission settings.

Environment

  • Claude Code: Latest version
  • - Platform: macOS (but affects all platforms based on related issues)
  • - - Configuration: Project-level .claude/settings.json

Priority

Medium - Would be very helpful

Feature Category

Configuration and settings

Use Case Example

  1. I'm developing a multi-tenant SaaS application with Claude Code
  2. 2. I grant broad Bash permissions so Claude can run tests, linters, build commands without constant prompts
  3. 3. I create a PreToolUse hook that detects git push and git merge commands
  4. 4. Hook returns {"permissionDecision": "ask", "reason": "About to push to remote - please confirm"}
  5. 5. Expected: Claude pauses and asks me to confirm before pushing
  6. 6. Actual: Hook is bypassed, push executes immediately without confirmation
  7. 7. This matters because an accidental push to production could deploy untested code

Additional Context

_No response_

View original on GitHub ↗

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