hookify plugin: internal `from hookify.*` imports fail — no `hookify/` package directory shipped

Resolved 💬 2 comments Opened May 27, 2026 by saxster Closed May 30, 2026

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

  1. Install hookify.
  2. Drop any ~/.claude/hookify.example.local.md rule (or use one of the shipped examples).
  3. Trigger any tool call (Bash, Edit, etc.).
  4. 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗