PreToolUse hooks cannot block tool execution - approve: false is ignored

Resolved 💬 2 comments Opened Jul 25, 2025 by bankielewicz Closed Jul 25, 2025

Environment

  • Platform (select one):
  • [X] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <!-- specify -->
  • Claude CLI version: <!-- Run claude --version to get this -->
  • Operating System: Windows 11
  • Terminal: Git Bash (via Windows Terminal/PowerShell)

Bug Description

PreToolUse hooks cannot block tool execution in Claude Code CLI. When a PreToolUse hook returns {"approve": false}, the tool operation proceeds anyway, defeating the purpose of pre-execution validation hooks.

Steps to Reproduce

  1. Create a PreToolUse hook that validates content and returns {"approve": false} for certain patterns:

```javascript
// .bmad-core/hooks/claude-code/pre-tool-use.js
async function readStdinJson() {
return new Promise((resolve) => {
let data = '';
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
resolve({});
}
});
});
}

const input = await readStdinJson();
const toolName = input.tool_name || '';
const toolInput = input.tool_input || {};

if (toolName === 'Write') {
const content = toolInput.content || '';
if (content.includes('TODO: implement')) {
console.log(JSON.stringify({
approve: false,
message: "BMAD Reality Guard: Detected simulation pattern."
}));
return;
}
}
console.log(JSON.stringify({ approve: true }));
```

  1. Configure the hook in .claude/settings.json:

``json
{
"hooks": {
"PreToolUse": [
{
"name": "BMAD Write Validator",
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node \".bmad-core\\hooks\\claude-code\\pre-tool-use.js\""
}
]
}
]
}
}
``

  1. Run Claude Code with debug flag: claude --debug
  1. Try to create a file with stub content:

``
Write(test-stub.js)
function testFunction() {
// TODO: implement this
throw new Error("Not implemented");
}
``

Expected Behavior

The file creation should be blocked with the rejection message displayed, and the file should NOT be created.

Actual Behavior

Debug output shows:

[DEBUG] Hook stdout: {"approve":false,"message":"BMAD Reality Guard: Detected simulation pattern..."}
[DEBUG] Successfully parsed and validated hook JSON output
[DEBUG] Writing to temp file: C:\Projects\HelloWorld\hook-test-stub4.js.tmp.46428.1753411839555
[DEBUG] File C:\Projects\HelloWorld\hook-test-stub4.js written atomically

The file is created despite the hook returning {"approve": false}.

Additional Context

Debug Log Evidence

Claude Code Debug Output

From the --debug session when attempting to create stub-test-new.js:

[DEBUG] Executing hook command: node ".bmad-core\hooks\claude-code\pre-tool-use.js" with timeout 60000ms
[DEBUG] Hook command completed with status 0: node ".bmad-core\hooks\claude-code\pre-tool-use.js"
[DEBUG] Hook stdout: {"approve":false,"message":"BMAD Reality Guard: Detected simulation pattern. Please provide complete, functional implementation. No stubs, mocks, or placeholders allowed in production code."}
...
[DEBUG] Successfully parsed and validated hook JSON output
[DEBUG] Parsed JSON output from hook: {}
[DEBUG] Processed hook result: {}
[DEBUG] Writing to temp file: C:\Projects\HelloWorld\stub-test-new.js.tmp.30816.1753414288545
[DEBUG] Temp file written successfully, size: 587 bytes
[DEBUG] File C:\Projects\HelloWorld\stub-test-new.js written atomically
Hook Debug Log Evidence

From pre-tool-use-debug.log showing the hook's internal processing:

2025-07-25T03:29:28.074Z - readStdinJson: Received chunk 1, size: 962 bytes
2025-07-25T03:29:28.092Z - Full input: {"session_id":"e9adc0a4-9b53-4a8b-9f47-5cddb48fe448",...,"tool_name":"Write","tool_input":{"file_path":"C:\\Projects\\HelloWorld\\stub-test-new.js","content":"// Test file for BMAD Reality Guard hook validation\nfunction processUserData() {\n  // TODO: implement user data processing\n  throw new Error(\"Not impleme...
2025-07-25T03:29:28.099Z - Processing Write operation
2025-07-25T03:29:28.105Z - Strict mode is enabled, proceeding with validation
2025-07-25T03:29:28.111Z - Testing pattern 0: /\/\/\s*TODO:?\s*[Ii]mplement/i
2025-07-25T03:29:28.113Z - PATTERN MATCHED! Pattern 0: /\/\/\s*TODO:?\s*[Ii]mplement/i
2025-07-25T03:29:28.116Z - BLOCKED: Simulation pattern detected
2025-07-25T03:29:28.123Z - Returning rejection: {"approve":false,"message":"BMAD Reality Guard: Detected simulation pattern. Please provide complete, functional implementation. No stubs, mocks, or placeholders allowed in production code."}
2025-07-25T03:29:28.126Z - Rejection sent to stdout, exiting validateBeforeWrite

Additional Findings

  1. Documentation Discrepancy: Hooks receive input via stdin, not process.argv[2] as some examples suggest
  2. Initial Configuration Issue: Custom configuration keys (like bmad-hooks) in settings.json cause validation errors and must be moved to separate files
  3. Hook Execution Confirmed: After fixing configuration issues, hooks do execute (verified via debug logs and file creation)
  4. PostToolUse and Stop hooks work correctly: Only PreToolUse approval mechanism is broken

Impact

This bug makes PreToolUse hooks effectively useless for their intended purpose of preventing operations based on validation criteria. Use cases affected:

  • Code quality enforcement
  • Security validation
  • Content policy enforcement
  • Custom validation workflows

Workaround

Currently, PreToolUse hooks can only serve as logging/warning mechanisms but cannot actually prevent tool execution.

View original on GitHub ↗

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