PreToolUse hook `permissionDecision: "deny"` ignored for Edit tool — tool executes despite deny

Resolved 💬 4 comments Opened Mar 21, 2026 by joseph-holland Closed Mar 21, 2026

Bug

A PreToolUse hook returning a well-formed deny response (correct JSON, permissionDecision: "deny", exit code 2) is ignored for the Edit tool. Claude proceeds to execute the edit and the file is modified.

This is a governance/security concern — hooks are the only mechanism external tools have to enforce policy on tool execution.

Reproduction

  1. Configure a PreToolUse hook that denies Edit calls to a specific file:
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "vectimus hook --source claude-code"
      }]
    }]
  }
}
  1. The hook returns this on stdout with exit code 2:
{"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked by policy: Block writes to governance config files"}
  1. Verified the output manually:
$ echo '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/me/.claude/settings.json","old_string":"test","new_string":"test2"}}' | vectimus hook --source claude-code 2>/dev/null
{"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked by policy vectimus-fileint-004: Block writes to governance config files to prevent policy bypass"}
$ echo $?
2
  1. Despite the deny, Claude executes the Edit and the file is modified.

Evidence

Audit log shows the hook evaluated and returned deny. There is no second evaluation — Claude did not retry. It received the deny and proceeded anyway.

19:19:59  Read  → file_read   → allow  file=/Users/me/.claude/settings.json
19:20:06  Edit  → file_write  → deny   file=/Users/me/.claude/settings.json

The file was modified at 19:20:06 despite the deny.

Expected behavior

When a PreToolUse hook returns permissionDecision: "deny" with exit code 2, the tool MUST NOT execute. The deny should be surfaced to the model as a blocked action.

Related issues

  • #31592 — Distinguish hook denial from hook error in PreToolUse output
  • #33106 — PreToolUse hook permissionDecision: "deny" not enforced for MCP server tool calls

The pattern seems to be that PreToolUse hook denials are not reliably enforced across tool types.

This is critical for any external governance tool (Vectimus, Guardrails, custom enterprise hooks) that relies on PreToolUse to enforce policy. If deny is advisory rather than mandatory, the hook mechanism cannot provide security guarantees.

Environment

  • Claude Code version: latest (via CLI)
  • Platform: macOS (Darwin 25.3.0)
  • Hook: Vectimus governance engine (returns well-formed deny JSON + exit 2)

View original on GitHub ↗

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