Skip to content

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

@joseph-holland

Description

@joseph-holland

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

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:hooksbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions