Skip to content

fix(hookify): drop hookify.* import prefix so hooks resolve in versioned cache#54429

Open
Codeturion wants to merge 1 commit intoanthropics:mainfrom
Codeturion:fix/hookify-imports
Open

fix(hookify): drop hookify.* import prefix so hooks resolve in versioned cache#54429
Codeturion wants to merge 1 commit intoanthropics:mainfrom
Codeturion:fix/hookify-imports

Conversation

@Codeturion
Copy link
Copy Markdown

Summary

Hookify's four hook scripts and core/rule_engine.py use from hookify.core.X and from hookify.X imports. The plugin's source layout has core/, matchers/, utils/ as direct children of the plugin root with no hookify/ wrapper directory, so these imports only resolve when something puts a literal hookify directory on sys.path. The hook scripts try to do this by adding the parent of CLAUDE_PLUGIN_ROOT to sys.path. That coincidentally works in dev mode (where parent(plugins/hookify) == plugins/, which contains a hookify/ directory — the plugin root itself) but fails in the production cache layout (.../hookify/0.1.0/), where the parent contains only the version directory. All four hooks then fail import, emit {"systemMessage": "Hookify import error: No module named 'hookify'"}, and exit 0 — the plugin silently no-ops for every user with a cache install.

Drop the hookify. prefix from the 10 import statements and rely on the existing sys.path.insert(0, PLUGIN_ROOT) so core, matchers, and utils resolve as top-level packages. Remove the misleading parent_dir block.

Files changed

  • plugins/hookify/hooks/pretooluse.py
  • plugins/hookify/hooks/posttooluse.py
  • plugins/hookify/hooks/stop.py
  • plugins/hookify/hooks/userpromptsubmit.py — same edit in all four hook scripts: remove the parent_dir block, simplify the if PLUGIN_ROOT guard, drop hookify. from the two imports.
  • plugins/hookify/core/rule_engine.py — drop hookify. from the module-level import (line 10) and the __main__ test import (line 278).

Verification

Setup. Mirror the production cache layout the issues describe: copy the patched plugin tree to notes/repro-hookify/cache/hookify/0.1.0/, then run each hook script with CLAUDE_PLUGIN_ROOT pointing at that path and a minimal valid PreToolUse JSON payload on stdin. Run the same set against the dev layout (plugins/hookify directly) to check for regressions. Repro script: notes/repro-hookify/verify.sh.

Details. Pre-fix runs were captured against git show main: versions of the same five files; post-fix runs against this branch. Each hook either prints {"systemMessage": "Hookify import error: ..."} (broken) or {} (working — empty rule evaluation, since no .claude/hookify.*.local.md files exist in the run cwd). Neither is exercised through the live Claude Code harness (no spacey-cache install available locally), but the harness invokes the hook scripts via python3 <path> with CLAUDE_PLUGIN_ROOT set, which is exactly what the verification reproduces.

Comparisons.

Scenario Pre-fix Post-fix
pretooluse.py under .../hookify/0.1.0/ Hookify import error: No module named 'hookify', exit 0 {}, exit 0
posttooluse.py under .../hookify/0.1.0/ same import error {}, exit 0
stop.py under .../hookify/0.1.0/ same import error {}, exit 0
userpromptsubmit.py under .../hookify/0.1.0/ same import error {}, exit 0
pretooluse.py under plugins/hookify/ (dev layout, regression check) {}, exit 0 {}, exit 0
posttooluse.py, stop.py, userpromptsubmit.py under dev layout {}, exit 0 each {}, exit 0 each
Hook with CLAUDE_PLUGIN_ROOT unset graceful: Hookify import error: No module named 'hookify', exit 0 graceful: Hookify import error: No module named 'core', exit 0

Regression notes. rule_engine.py's __main__ test path was never invokable via bare python3 core/rule_engine.py from the plugin root (verified against upstream main: ModuleNotFoundError: No module named 'hookify'). It required PYTHONPATH=plugins python3 plugins/hookify/core/rule_engine.py before, and works now via cd plugins/hookify && python3 -m core.rule_engine. Net brokenness is unchanged; the required incantation is updated. The graceful-degradation path (try/except ImportError → systemMessage → exit 0) is preserved end-to-end; only the error message text changes ('hookify''core' when imports legitimately can't resolve).

Refs

Closes #47868
Closes #33409

…ned cache

Each hook script's sys.path setup added the parent of CLAUDE_PLUGIN_ROOT in
the hope that Python would find a hookify package there. That works in dev
mode (parent dir of plugins/hookify is plugins/, which literally contains
hookify/ as a subdirectory) but fails in production where the cache layout
puts the plugin at .../hookify/0.1.0/ — the parent dir contains only the
version directory. All four hooks then fail import, emit a systemMessage,
and exit 0; the plugin silently no-ops for every cache install.

Drop the hookify. prefix from the imports across the four hook scripts and
the core rule_engine module, and rely on the existing
sys.path.insert(0, PLUGIN_ROOT) so that core, matchers, and utils resolve
as top-level packages. The dead parent_dir block (which was the misleading
mechanism) is removed and the comment updated.

Closes anthropics#47868. Closes anthropics#33409.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant