hookify plugin: internal `from hookify.*` imports fail — no `hookify/` package directory shipped
Plugin: plugins/hookify (v0.1.0, author: @daisy)
Summary
The hookify plugin's hook scripts and internal modules import via from hookify.*, but the plugin ships its code directly at the plugin root — there is no hookify/ subdirectory on disk. Result: every hook invocation silently fails with No module named 'hookify', and all user-defined ~/.claude/hookify.*.local.md rules are dormant.
Affected imports (9 total)
hooks/pretooluse.py:26 from hookify.core.config_loader import load_rules
hooks/pretooluse.py:27 from hookify.core.rule_engine import RuleEngine
hooks/posttooluse.py:22-23 (same two)
hooks/stop.py:22-23 (same two)
hooks/userpromptsubmit.py:22-23 (same two)
core/rule_engine.py:10 from hookify.core.config_loader import Rule, Condition
core/rule_engine.py:278 (duplicate conditional import)
Why the existing sys.path setup doesn't rescue it
Each hook script does:
PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT') # .../hookify/0.1.0/
sys.path.insert(0, os.path.dirname(PLUGIN_ROOT)) # .../hookify/
sys.path.insert(0, PLUGIN_ROOT) # .../hookify/0.1.0/
Neither path contains a hookify/__init__.py, so the hookify.* namespace never resolves. The import falls through to:
except ImportError as e:
error_msg = {"systemMessage": f"Hookify import error: {e}"}
print(json.dumps(error_msg), file=sys.stdout)
sys.exit(0)
…which exits 0 and looks like a successful hook invocation, masking the failure.
Reproduce
- Install hookify.
- Drop any
~/.claude/hookify.example.local.mdrule (or use one of the shipped examples). - Trigger any tool call (Bash, Edit, etc.).
- Inspect the system message stream — you'll see
Hookify import error: No module named 'hookify'.
No rule ever fires.
Recommended fixes
Option A (preferred, no code changes): move core/, hooks/, utils/, matchers/, etc. into a hookify/ subdirectory at the plugin root. Update hooks.json command paths to ${CLAUDE_PLUGIN_ROOT}/hookify/hooks/pretooluse.py. Existing imports work unchanged.
Option B: convert internal imports to relative form (from .core.config_loader import ...) and have hooks/*.py bootstrap themselves as a package via __package__ assignment before relative imports. More invasive, fragile across Python versions.
Option A matches what the imports already assume and is the standard Python package layout.
Local workaround (for users hitting this now)
Until upstream lands a fix:
P=~/.claude/plugins/cache/claude-code-plugins/hookify/0.1.0
touch "$P/__init__.py"
ln -s . "$P/hookify"
This makes the plugin root itself importable as the hookify package via a self-symlink. Survives until the next /plugin update, which wipes the versioned directory. A SessionStart hook that re-applies the shim makes it permanent on a per-machine basis.
Happy to send a PR for Option A if useful.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗