[BUG] hookify plugin fails with "No module named 'hookify'" on every hook invocation

Status Open
Reported on v2.1.220
Maintainer reply ✓ Yes — claude[bot]
Activity 5 comments · opened Jul 26, 2026
💡 Likely answer: A maintainer (claude[bot], contributor) responded on this thread — see the highlighted reply below.

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?

Every hook invocation (PreToolUse, PostToolUse, UserPromptSubmit, Stop) from the hookify plugin fails with an import error: "Hookify import error: No module named 'hookify'". It prints on every single tool call and session event, but doesn't block execution — meaning hookify's rules never actually get evaluated or enforced, silently.

What Should Happen?

Hookify's hook scripts should successfully import their internal modules and evaluate configured .local.md rules without printing an import error.

Error Messages/Logs

Hookify import error: No module named 'hookify'

Steps to Reproduce

  1. Install hookify: /plugin marketplace add anthropics/claude-code, then /plugin install hookify@claude-code-plugins
  2. Create any rule file in .claude/, e.g. .claude/hookify.test.local.md with a basic "warn on file" rule
  3. Trigger any Write, Edit, Bash, or prompt submission
  4. Every PreToolUse/PostToolUse/UserPromptSubmit/Stop hook prints "Hookify import error: No module named 'hookify'" instead of evaluating the rule

Root cause (found by inspecting the installed plugin): the hook scripts do from hookify.core.config_loader import load_rules, but the actual installed layout is .../claude-code-plugins/hookify/0.1.0/core/config_loader.py — there's no hookify package directory wrapping core/, it sits directly under the versioned 0.1.0 folder. Neither PLUGIN_ROOT nor its parent on sys.path makes hookify.core.config_loader resolve. Should be from core.config_loader import load_rules instead, using PLUGIN_ROOT which is already on sys.path.

The failure is wrapped in a bare exception handler that prints the message and exits 0, so rules are silently never evaluated rather than causing a hard failure.

Claude Model

None

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

version 2.1.220.

Platform

Other

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

Plugin: hookify 0.1.0, installed via the claude-code-plugins marketplace (anthropics/claude-code repo, plugins/hookify). Installed path: ~/.claude/plugins/cache/claude-code-plugins/hookify/0.1.0/

View original on GitHub ↗

3 Comments

zhian14 · 28 days ago

Confirmed on macOS 26.6, Claude Code 2.1.220 — same failure, so it's layout-generic, not Windows-specific. Two data points to add:

1. This is a same-version regression of #47868's fix, not "never worked". My installed cache copy of hookify 0.1.0 (fetched weeks ago) had the working relative imports (from core.config_loader import load_rules) and evaluated rules correctly — verified by invoking the hook entrypoint directly. Running claude plugin install hookify@claude-code-plugins --scope project for a second project today re-synced the shared cache in place (still version 0.1.0, files re-stamped) and replaced them with the from hookify.core.* style — which instantly and silently killed every hookify rule in all projects sharing that cache. Same plugin version, two different published behaviors: any marketplace refresh swaps working → broken with no version signal. (#62679 / #69665 were earlier reports of the same import bug, closed as dups of #47868.)

2. No-code-edit workaround — one symlink makes import hookify resolve as a namespace package inside the versioned cache layout:

ln -s 0.1.0 ~/.claude/plugins/cache/claude-code-plugins/hookify/hookify

All four hook scripts share the same import block, so this heals PreToolUse/PostToolUse/UserPromptSubmit/Stop at once. Verified end-to-end by piping probe JSON through CLAUDE_PLUGIN_ROOT=~/.claude/plugins/cache/claude-code-plugins/hookify/0.1.0 python3 .../hooks/pretooluse.py: warn and block rules fire again on matching input, {} on non-matching. (Caveat: a future re-sync deletes the symlink along with the cache dir — it needs re-creating until the imports are fixed upstream.)

Also worth noting the failure mode is doubly silent for action: warn rules: the import error goes to systemMessage, which per #20747 is rendered user-side only — from the model's perspective, configured rules simply cease to exist.

Stratogain · 15 days ago

Confirmed on Windows 11 (Claude Code 2.1.233, hookify 0.1.0 from the marketplace cache). Two independent failure layers on this platform, plus a root-cause isolation experiment for the import error.

1. Root cause of No module named 'hookify': the versioned cache dir name breaks the import contract.

hooks/pretooluse.py does from hookify.core.config_loader import load_rules and inserts the parent of CLAUDE_PLUGIN_ROOT into sys.path (the comment in the code says: so Python can find the "hookify" package). That contract requires the plugin directory itself to be named hookify. But the marketplace installs into a versioned path:

~/.claude/plugins/cache/claude-code-plugins/hookify/0.1.0/   <- CLAUDE_PLUGIN_ROOT

so parent_dir is .../hookify/, which contains only 0.1.0/ — nothing importable as hookify.

Isolation experiment — copy the cache dir to a folder literally named hookify and run the same hook with the same stdin:

cp -r ~/.claude/plugins/cache/claude-code-plugins/hookify/0.1.0 /tmp/probe/hookify
echo '{"session_id":"t","cwd":"/tmp","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo hi"}}' \
  | CLAUDE_PLUGIN_ROOT=/tmp/probe/hookify python /tmp/probe/hookify/hooks/pretooluse.py
# -> {}   (exit 0)

Same bytes, only the directory name differs — the code itself is fine; the layout assumption is what's broken. This is consistent with @zhian14's observation that an older cache copy using relative imports (from core.config_loader import ...) worked: relative imports (or inserting CLAUDE_PLUGIN_ROOT itself with matching package layout) would fix this regardless of how the cache names version directories.

2. Windows-only and independent of #1: python3 in hooks.json resolves to the Microsoft Store alias.

All four hook commands are python3 ${CLAUDE_PLUGIN_ROOT}/hooks/....py. On stock Windows, python3 in PATH is the App Execution Alias (.../WindowsApps/python3.exe -> AppInstallerPythonRedirector.exe), which prints Python and exits 49 without ever running the script. A real CPython install provides python and py, but no python3 name. Run exactly the way Claude Code invokes hooks:

$ echo '{...}' | sh -c "python3 .../hooks/pretooluse.py"
Python
# exit 49

So on Windows the hooks die before even reaching the import bug. The plugin needs a python/py fallback (or interpreter resolution) to work on Windows at all.

Both failures are soft (message + non-blocking exit), so configured rules are silently never enforced — matching the "silently" wording in the OP.

claude[bot] contributor · 12 days ago

Confirmed / reproduced on Claude Code 2.1.234 (Linux): claude plugin marketplace add anthropics/claude-code + claude plugin install hookify@claude-code-plugins installs hookify 0.1.0 into the plugin cache under a versioned hookify/0.1.0/ directory, and feeding a PreToolUse (Bash rm -rf …, with a matching .claude/hookify.*.local.md rule) or Stop payload to the installed hook scripts prints

{"systemMessage": "Hookify import error: No module named 'hookify'"}

every time, so no rule is ever evaluated. For comparison, hookify@claude-plugins-official installed the same way evaluates the same rule file correctly (the bash rule fires), so switching to that marketplace's copy is a workaround until this one is fixed.

🤖 Generated with Claude Code

---
_Generated by Claude Code_

Showing cached comments. Read the full discussion on GitHub ↗