Skip to content

fix(hookify): make hook entrypoints runnable without CLAUDE_PLUGIN_ROOT - #79889

Open
adelaidasofia wants to merge 1 commit into
anthropics:mainfrom
adelaidasofia:hookify-plugin-root-fallback
Open

fix(hookify): make hook entrypoints runnable without CLAUDE_PLUGIN_ROOT#79889
adelaidasofia wants to merge 1 commit into
anthropics:mainfrom
adelaidasofia:hookify-plugin-root-fallback

Conversation

@adelaidasofia

Copy link
Copy Markdown

Summary

All four hook entrypoints add the plugin to sys.path only when CLAUDE_PLUGIN_ROOT is set:

PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
if PLUGIN_ROOT:
    ... sys.path.insert(...)

When the variable is absent the guard silently skips path setup, the following from hookify.core... import fails, and the hook prints {"systemMessage": "Hookify import error: No module named 'hookify'"} and exits 0 — every rule for that event is inert.

The practical consequence is that the hooks are not runnable outside the plugin harness. Piping a payload into pretooluse.py to test a rule fails unless the caller happens to know to set the variable first, which makes local rule development and debugging harder than it needs to be. (I hit this twice while testing rules against this plugin, and it silently masked an unrelated result until I noticed the import error string in the output.)

Fix

Fall back to the file's own location:

PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT') or os.path.dirname(
    os.path.dirname(os.path.abspath(__file__))
)

The if PLUGIN_ROOT: guard becomes unnecessary (the value is now always resolvable), so its body is dedented. Behavior is unchanged when CLAUDE_PLUGIN_ROOT is set — the env var still wins.

Applied identically to pretooluse.py, posttooluse.py, stop.py, and userpromptsubmit.py.

Verification

Real payload (rm -rf against a event: bash rule), official vs patched, with and without the variable:

build CLAUDE_PLUGIN_ROOT set result
official yes rule fires
official no import error (all rules inert)
patched yes rule fires
patched no rule fires

The other three entrypoints were each driven with their own payload without the variable set and returned clean {} instead of an import error. python3 -m py_compile clean on all four.

🤖 Generated with Claude Code

All four hook entrypoints add the plugin to sys.path only when
CLAUDE_PLUGIN_ROOT is set:

    PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
    if PLUGIN_ROOT:
        ... sys.path.insert(...)

When the variable is absent the guard silently skips path setup, the
following `from hookify.core...` import fails, and the hook prints
{"systemMessage": "Hookify import error: No module named 'hookify'"} and
exits 0 - every rule for that event is inert.

The practical consequence is that the hooks are not runnable outside the
plugin harness. Piping a payload into pretooluse.py to test a rule fails
unless the caller happens to know to set the variable first, which makes
local rule development and debugging harder than it needs to be.

Fall back to the file's own location. Behavior is unchanged when
CLAUDE_PLUGIN_ROOT is set.

Verified on all four entrypoints with a real payload:
  official + env var -> rule fires;  official without -> import error
  patched  + env var -> rule fires;  patched  without -> rule fires

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant