[BUG] hookify rule files containing non-ASCII are silently skipped on Windows (open() without encoding)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
plugins/hookify/core/config_loader.py opens rule files with open(file_path, 'r') and no encoding, so Python uses the locale default. On Windows that is cp1252, and a rule file containing any byte cp1252 cannot map raises UnicodeDecodeError. That subclasses ValueError, so it is swallowed by the except (ValueError, KeyError, AttributeError, TypeError) handler in load_rule_file, which returns None. The rule is dropped and the hook proceeds.
This defeats action: block rules. The plugin's own shipped examples/dangerous-rm.local.md contains a warning emoji (e2 9a a0 ef b8 8f), so following the documented example produces a block rule that never blocks.
This is separate from #81448, and it affects the copy in this repo and the one in anthropics/claude-plugins-official equally; neither passes an encoding.
What Should Happen?
Rule files should be read as UTF-8 regardless of platform locale, so a rule containing non-ASCII characters loads and is enforced.
Error Messages/Logs
Error: Malformed rule file .claude\hookify.dangerous-rm.local.md: 'charmap' codec can't decode byte 0x8f in position 97: character maps to <undefined>
This goes to stderr. stdout carries no indication that a rule was dropped.
Steps to Reproduce
- Copy the plugin's own example in as a rule:
plugins/hookify/examples/dangerous-rm.local.mdto<project>/.claude/hookify.dangerous-rm.local.md. It isaction: blockon patternrm\s+-rf. - On Windows, feed a matching PreToolUse payload to the hook script:
````
{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf /"},"cwd":"<project>","session_id":"t"}
- Observed: stdout is
{}, so the command is allowed. stderr shows the charmap error above. - Change
core/config_loader.py:251toopen(file_path, 'r', encoding='utf-8')and repeat. - Observed:
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny"}, ...}and the rule fires as documented.
This reproduces where the locale default is not UTF-8. On macOS/Linux with a UTF-8 locale it does not, which is likely why it has gone unreported.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Claude Code Version
2.1.238
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Other
Additional Information
Python 3.13, locale.getpreferredencoding(False) returns cp1252. Python 3.15 makes UTF-8 mode the default (PEP 686), which would mask this. hookify 0.1.0, installed from the claude-code-plugins marketplace.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Tested patch available
I prepared a fix that reads Hookify rule files with an explicit UTF-8 encoding and handles
UnicodeDecodeErrorbefore the broaderValueErrorcase. This keeps non-ASCII rules working on Windows and preserves the intended diagnostic for malformed input.Verification completed:
PYTHONUTF8=0 LC_ALL=Cto simulate an ASCII platform default.git diff --checkpassed.Links:
The repository currently limits new pull requests to collaborators, so GitHub would not let me open the PR directly. A maintainer can cherry-pick
048045b297267cd8e09f2a1ebe39cd97e5cc695bor open a PR fromProxypro2012:codex/fix-hookify-utf8.Same root cause as #77270, and open PR #79645 already adds
encoding='utf-8'to thisopen()and to the transcript read inrule_engine.py.I have a fix ready on my fork: sepehrtaji-dev:fix/hookify-utf8-encoding
The patch adds
encoding='utf-8'to both locale-defaultopen()calls in the plugin:core/config_loader.pyload_rule_file()(the site named in this issue)core/rule_engine.py_get_input_value()transcript read (identical failure mode for transcripts containing non-ASCII text)Verified on Windows by loading a rule file containing the ⚠️ emoji through
load_rule_file(): before the patch the rule is silently dropped by theexcept (ValueError, ...)handler; after the patch it parses and theaction: blockrule fires with the message text preserved.PR creation appears to be limited to collaborators on this repository at the moment, so I'm linking the branch here instead — happy to open a proper PR if invited, or feel free to apply the two-line change directly.