From f259be4793ad56be337ba7feeca1a337b9048e02 Mon Sep 17 00:00:00 2001 From: Katherine Wren Date: Fri, 27 Mar 2026 11:18:52 +0000 Subject: [PATCH] fix: handle 'prompt' key for UserPromptSubmit and deduplicate rule loading Claude Code's UserPromptSubmit hook sends the user's message under the key 'prompt', not 'user_prompt'. The rule engine was always reading an empty string for the user_prompt field, causing all prompt-event rules (use-superpowers, decision-capture, meetsync-reminder, etc.) to silently never fire. Also fixes duplicate rule loading: when the hook runs with cwd=$HOME, the project-level pattern (.claude/hookify.*.local.md) and the global pattern (~/.claude/hookify.*.local.md) resolve to the same files. Added deduplication by resolved absolute path before loading. Co-Authored-By: Claude Sonnet 4.6 --- core/config_loader.py | 7 ++++++- core/rule_engine.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/config_loader.py b/core/config_loader.py index d31827a..b76f2c4 100644 --- a/core/config_loader.py +++ b/core/config_loader.py @@ -219,9 +219,14 @@ def load_rules(event: Optional[str] = None) -> List[Rule]: os.path.join('.claude', 'hookify.*.local.md'), # Project-level os.path.expanduser(os.path.join('~', '.claude', 'hookify.*.local.md')), # Global ] + seen = set() files = [] for pattern in patterns: - files.extend(glob.glob(pattern)) + for f in glob.glob(pattern): + resolved = os.path.realpath(f) + if resolved not in seen: + seen.add(resolved) + files.append(f) for file_path in files: try: diff --git a/core/rule_engine.py b/core/rule_engine.py index f52ce26..309f83d 100644 --- a/core/rule_engine.py +++ b/core/rule_engine.py @@ -227,8 +227,8 @@ def _extract_field(self, field: str, tool_name: str, print(f"Warning: Encoding error in transcript {transcript_path}: {e}", file=sys.stderr) return '' elif field == 'user_prompt': - # For UserPromptSubmit events - return input_data.get('user_prompt', '') + # For UserPromptSubmit events — Claude Code sends 'prompt', not 'user_prompt' + return input_data.get('user_prompt') or input_data.get('prompt', '') # Handle special cases by tool type if tool_name == 'Bash':