Skip to content

Commit 16ffdee

Browse files
author
Joseph Edmonds
committed
Merge branch 'php8.4' of https://github.com/LongTermSupport/php-qa-ci into php8.4
2 parents 09d5288 + e9582f2 commit 16ffdee

11 files changed

Lines changed: 541 additions & 211 deletions

File tree

.claude/hooks/CLAUDE.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,119 @@ If hooks fail with Python errors:
216216
2. Check for syntax errors: `python3 -m py_compile .claude/hooks/<hook>.py`
217217
3. Review hook output in Claude Code for error messages
218218

219+
### Hook Response Schema Errors
220+
221+
**CRITICAL**: Different hook event types require DIFFERENT response schemas!
222+
223+
#### Stop Hook Schema
224+
225+
```json
226+
{
227+
"continue": true,
228+
"stopReason": "Why stopping/continuing",
229+
"decision": "approve" | "block"
230+
}
231+
```
232+
233+
- `continue: true` = block the stop, keep running
234+
- `continue: false` = approve the stop, let Claude stop
235+
- `decision: "block"` = block the stop
236+
- `decision: "approve"` = approve the stop
237+
238+
#### PreToolUse/PostToolUse Schema
239+
240+
```json
241+
{
242+
"hookSpecificOutput": {
243+
"hookEventName": "PreToolUse",
244+
"permissionDecision": "allow" | "deny",
245+
"permissionDecisionReason": "Why"
246+
}
247+
}
248+
```
249+
250+
**Common Error**: Using `hookSpecificOutput` for Stop events causes:
251+
```
252+
Stop hook error: JSON validation failed: Hook JSON output validation failed
253+
```
254+
255+
## Dedicated Entry Points Pattern (CRITICAL)
256+
257+
When a hook may be called from multiple event types (Stop, PreToolUse, PostToolUse), it MUST have dedicated entry points for each. DO NOT try to infer the event type from input - it's unreliable.
258+
259+
### Pattern
260+
261+
```python
262+
def make_response(event_name: str, decision: str, reason: str) -> dict:
263+
"""Create response with correct schema for event type."""
264+
if event_name == "Stop":
265+
# Stop uses completely different schema!
266+
return {
267+
"continue": decision == "deny", # deny stop = continue
268+
"stopReason": reason,
269+
"decision": "block" if decision == "deny" else "approve"
270+
}
271+
else:
272+
# PreToolUse/PostToolUse use hookSpecificOutput
273+
return {
274+
"hookSpecificOutput": {
275+
"hookEventName": event_name,
276+
"permissionDecision": decision,
277+
"permissionDecisionReason": reason
278+
}
279+
}
280+
281+
def process_hook_logic(event_name: str) -> None:
282+
"""Core logic - event_name passed explicitly, NOT inferred."""
283+
# ... your logic here ...
284+
output_and_exit(make_response(event_name, "allow", "Reason"))
285+
286+
# DEDICATED ENTRY POINTS - one per event type
287+
def main_stop() -> None:
288+
process_hook_logic("Stop")
289+
290+
def main_pre_tool_use() -> None:
291+
process_hook_logic("PreToolUse")
292+
293+
def main() -> None:
294+
"""Default entry - uses CLAUDE_HOOK_EVENT env var."""
295+
event = os.environ.get("CLAUDE_HOOK_EVENT")
296+
if event:
297+
process_hook_logic(event)
298+
return
299+
main_stop() # Default to this hook's primary purpose
300+
```
301+
302+
### Registering for Multiple Events
303+
304+
Use `CLAUDE_HOOK_EVENT` environment variable in settings.json:
305+
306+
```json
307+
{
308+
"hooks": {
309+
"Stop": [{
310+
"hooks": [{
311+
"type": "command",
312+
"command": "CLAUDE_HOOK_EVENT=Stop .claude/hooks/my-hook.py"
313+
}]
314+
}],
315+
"PreToolUse": [{
316+
"hooks": [{
317+
"type": "command",
318+
"command": "CLAUDE_HOOK_EVENT=PreToolUse .claude/hooks/my-hook.py"
319+
}]
320+
}]
321+
}
322+
}
323+
```
324+
325+
### Why This Pattern?
326+
327+
1. **Correct Schema** - Each event type gets proper response format
328+
2. **Reliable** - Event name explicit, not inferred
329+
3. **Testable** - Each entry point tested independently
330+
4. **DRY** - Shared logic with event-specific response formatting
331+
219332
## Related Documentation
220333

221334
- **README.md** - Comprehensive hook documentation and usage guide

0 commit comments

Comments
 (0)