[Bug] PreToolUse hook permissionDecision ignored when tool in allow list

Resolved 💬 3 comments Opened Jan 15, 2026 by GabinMD Closed Jan 19, 2026

Bug Description
Bug Report: PreToolUse hooks permissionDecision is completely ignored when Bash is in allow list

Summary

When "Bash" is in the allow list in settings.json, PreToolUse hooks returning {"hookSpecificOutput": {"permissionDecision": "ask"}} or {"hookSpecificOutput": {"permissionDecision": "deny"}} are completely ignored. The command executes regardless of the hook's decision.

Environment

  • Claude Code CLI (not VS Code extension)
  • Linux (Debian/Ubuntu)
  • Settings location: ~/.claude/settings.json

Configuration

settings.json:
{
"permissions": {
"allow": ["Bash", "Read"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/root/.claude/hooks/check-dangerous-bash.sh"
}
]
}
]
}
}

Hook script (check-dangerous-bash.sh):
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if [[ "$COMMAND" == "git commit" ]]; then
echo '{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "Dangerous command blocked"}}'
exit 0
fi
exit 0

Steps to Reproduce

  1. Configure settings.json with allow: ["Bash"] and a PreToolUse hook for Bash
  2. Create a hook script that returns permissionDecision: "deny" for git commit commands
  3. Ask Claude to run git commit --dry-run -m "test"
  4. Observe that the command executes despite the hook returning "deny"

Expected Behavior

According to the documentation, hooks should be able to override permissions:

  • permissionDecision: "ask" should prompt the user for confirmation
  • permissionDecision: "deny" should block the command entirely

The hook IS being called (verified via debug logging), and it DOES return the correct JSON output, but Claude Code ignores it.

Actual Behavior

  • Hook is called ✅
  • Hook returns correct JSON with permissionDecision: "deny" ✅
  • Claude Code ignores the hook output and executes the command anyway ❌

Debug Evidence

# Hook is being called (from debug log):
[Thu Jan 15 09:25:33 UTC 2026] Hook called
[Thu Jan 15 09:25:33 UTC 2026] Input: {"tool_input":{"command":"git commit --dry-run -m \"test\""}, ...}

# Hook returns correct output:
$ echo '{"tool_input": {"command": "git commit -m test"}}' | ./check-dangerous-bash.sh
{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "Dangerous command blocked"}}

Use Case

I want to:

  1. Allow all Bash commands by default (to avoid constant permission prompts for every variation of safe commands)
  2. BUT require confirmation (ask) for dangerous commands like git commit, git push, rm -rf, etc.

Currently this is impossible because:

  • allow: ["Bash"] bypasses ask rules in permissions (documented as DENY > ALLOW > ASK priority)
  • PreToolUse hooks with permissionDecision are also bypassed when Bash is in allow list

Related Issues

  • #17159 - Bash command executed without asking for permission
  • #16710 - git commit executes without permission prompt
  • #13339 - VS Code Extension Ignores Hook permissionDecision: "ask"
  • #4669 - permissionDecision: "deny" in PreToolUse hooks is ignored

Suggested Fix

Hooks should take precedence over allow rules. The evaluation order should be:

  1. Hooks (if hook returns a decision, use it)
  2. Deny rules
  3. Ask rules
  4. Allow rules

This would allow users to have permissive defaults while still enforcing safety checks via hooks.

Environment Info

  • Platform: linux
  • Terminal: tmux
  • Version: 2.1.7
  • Feedback ID: 65c0057d-1afa-42ae-9089-a672bc1f00b8

Errors

[{"error":"Error: Error parsing line in /root/.claude/projects/-root-projects-tradozor/05be4838-02e2-4f1c-b364-23105cf7110a.jsonl: SyntaxError: Unexpected token 'p', \"np4Z+02UzSJ\"... is not valid JSON\n    at file:///root/.nvm/versions/node/v20.19.5/lib/node_modules/@anthropic-ai/claude-code/cli.js:74:79\n    at Array.map (<anonymous>)\n    at Fg (file:///root/.nvm/versions/node/v20.19.5/lib/node_modules/@anthropic-ai/claude-code/cli.js:74:26)\n    at async Mp (file:///root/.nvm/versions/node/v20.19.5/lib/node_modules/@anthropic-ai/claude-code/cli.js:4697:1984)\n    at async wP0 (file:///root/.nvm/versions/node/v20.19.5/lib/node_modules/@anthropic-ai/claude-code/cli.js:4697:3816)\n    at async x$7 (file:///root/.nvm/versions/node/v20.19.5/lib/node_modules/@anthropic-ai/claude-code/cli.js:4696:2995)\n    at async J8A

Note: Error logs were truncated.

View original on GitHub ↗

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