Skip to content

[hookify] BUG: Rules with specific event type (e.g: "stop") are loaded for all PreToolUse hook events when tool is not Bash/Edit/Write #71

@0xGosu

Description

@0xGosu

Bug: Rules with specific event type are loaded for all hook events when tool is not Bash/Edit/Write

Summary

Rules configured with a specific event type (e.g., event: stop) are incorrectly loaded and evaluated for unrelated hook events (e.g., PreToolUse) when the tool being used is not Bash, Edit, Write, or MultiEdit.

Environment

  • Plugin: hookify
  • Files affected:
    • hooks/pretooluse.py
    • core/config_loader.py

Steps to Reproduce

  1. Create a hookify rule with event: stop:
<!-- .claude/hookify.test-stop-only.local.md -->
---
name: test-stop-only
enabled: true
event: stop
action: block
conditions:
  - field: transcript
    operator: contains
    pattern: hello
---

This message should ONLY appear on Stop events, not PreToolUse.
  1. Start a Claude Code session and type "hello" in a message
  2. Observe that the warning appears when Claude uses tools like Task, Read, Glob, etc.

Expected Behavior

Rules with event: stop should only be evaluated during Stop hook events, not during PreToolUse, PostToolUse, or UserPromptSubmit events.

Actual Behavior

Rules with event: stop are evaluated during PreToolUse events when the tool is anything other than Bash, Edit, Write, or MultiEdit (e.g., Task, Read, Glob, Grep, WebFetch, etc.).

Root Cause Analysis

File: hooks/pretooluse.py (lines 37-44)

event = None
if tool_name == 'Bash':
    event = 'bash'
elif tool_name in ['Edit', 'Write', 'MultiEdit']:
    event = 'file'

rules = load_rules(event=event)  # event=None for other tools!

For tools not in the explicit list, event remains None.

File: core/config_loader.py (lines 219-221)

# Filter by event if specified
if event:  # BUG: When event=None, this entire block is skipped
    if rule.event != 'all' and rule.event != event:
        continue

When event=None, the filter condition if event: evaluates to False, so no filtering occurs. All enabled rules are returned regardless of their event setting.

Suggested Fix

Change the filter logic in config_loader.py to always filter rules with specific events:

# Always filter by event - rules with specific events should only match that event
if rule.event != 'all':
    if event is None or rule.event != event:
        continue

This ensures:

  • Rules with event: all continue to match all events (unchanged)
  • Rules with specific events (e.g., stop, bash, file) only match when that exact event is requested
  • When event=None is passed, only event: all rules are loaded

Alternative Fix

Update pretooluse.py to use a default event type for unhandled tools:

event = 'tool'  # Default for generic tools
if tool_name == 'Bash':
    event = 'bash'
elif tool_name in ['Edit', 'Write', 'MultiEdit']:
    event = 'file'

This would require users to use event: tool or event: all for generic tool matching.

Impact

  • Severity: Medium
  • User Impact: Unexpected warnings/blocks during normal tool usage
  • Workaround: Users must set tool_matcher to explicitly filter, but this doesn't fully solve the issue for Stop events which have no tool_name

Related

  • Affects all hook types that may pass event=None to load_rules()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions