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
- Configure a PreToolUse hook that denies Edit calls to a specific file:
{
"hooks": {
"PreToolUse": [{
"matcher": "",
"hooks": [{
"type": "command",
"command": "vectimus hook --source claude-code"
}]
}]
}
}
- The hook returns this on stdout with exit code 2:
{"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked by policy: Block writes to governance config files"}
- 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
- 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)
Bug
A PreToolUse hook returning a well-formed deny response (correct JSON,
permissionDecision: "deny", exit code 2) is ignored for theEdittool. 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
{ "hooks": { "PreToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "vectimus hook --source claude-code" }] }] } }{"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked by policy: Block writes to governance config files"}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.
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
permissionDecision: "deny"not enforced for MCP server tool callsThe 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