hookify plugin: prompt-event rules never fire on Windows (cp1252 encoding) + wrong field name 'user_prompt' vs 'prompt'
Bug
The hookify plugin's UserPromptSubmit rules silently never fire. The hook returns {} and injects nothing, so user-defined prompt gates (e.g. "invoke skill X before debugging") are dead. Because exit code is 0 with valid JSON, there is no signal to the agent that a rule failed to load.
Reproduced on Windows 11, Claude Code 2.1.207, plugin version 0.1.0.
Two root causes in plugins/hookify/core/
1. Encoding — files opened without encoding='utf-8'
config_loader.py:251—open(file_path, 'r')rule_engine.py:212—open(transcript_path, 'r')
On Windows the default codec is cp1252. Rule .local.md files are UTF-8 and commonly contain arrows, warning emoji, em-dashes. cp1252 cannot decode those bytes, so UnicodeDecodeError is caught in load_rule_file and the rule is silently dropped.
stderr from a real run:
Error: Malformed rule file .claude/hookify.warn-backup-before-edit.local.md: 'charmap' codec can't decode byte 0x8f in position 167: character maps to <undefined>
2. Wrong field name — kills every prompt rule
rule_engine.py:228:
elif field == 'user_prompt':
# For UserPromptSubmit events
return input_data.get('user_prompt', '')
Claude Code's UserPromptSubmit payload puts the prompt under the key prompt, not user_prompt. So field: user_prompt conditions always extract '', the regex never matches, and no rule fires. This affects all prompt-event rules regardless of the encoding fix.
Repro
PLUGIN_ROOT="$HOME/.claude/plugins/plugins/hookify"
echo '{"hook_event_name":"UserPromptSubmit","prompt":"debug this please","cwd":"."}' \
| CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" python3 "$PLUGIN_ROOT/hooks/userpromptsubmit.py"
# Before fix: stderr shows charmap codec errors, stdout = {}
With a rule whose frontmatter is:
event: prompt
conditions:
- field: user_prompt
operator: regex_match
pattern: (?i)debug
Suggested fix
# config_loader.py:251
with open(file_path, 'r', encoding='utf-8') as f:
# rule_engine.py:212
with open(transcript_path, 'r', encoding='utf-8') as f:
# rule_engine.py:228 - accept the key Claude Code actually sends
elif field == 'user_prompt':
return input_data.get('user_prompt') or input_data.get('prompt') or ''
The user_prompt or prompt fallback keeps existing user rule files (which the examples generate with field: user_prompt) working without requiring users to edit them.
Environment
- OS: Windows 11 (cp1252 default locale)
- Claude Code: 2.1.207
- Plugin: hookify 0.1.0 (
claude-code-pluginsmarketplace)