PreToolUse hooks cannot block tool execution - approve: false is ignored
Environment
- Platform (select one):
- [X] Anthropic API
- [ ] AWS Bedrock
- [ ] Google Vertex AI
- [ ] Other: <!-- specify -->
- Claude CLI version: <!-- Run
claude --versionto 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
- 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 }));
```
- 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\""
}
]
}
]
}
}
- Run Claude Code with debug flag:
claude --debug
- 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
- Documentation Discrepancy: Hooks receive input via stdin, not
process.argv[2]as some examples suggest - Initial Configuration Issue: Custom configuration keys (like
bmad-hooks) in settings.json cause validation errors and must be moved to separate files - Hook Execution Confirmed: After fixing configuration issues, hooks do execute (verified via debug logs and file creation)
- 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗