Skip to content

fix(plugins): quote ${CLAUDE_PLUGIN_ROOT} in hook commands, prefix hookify examples - #81670

Open
ozdemirsarman wants to merge 2 commits into
anthropics:mainfrom
ozdemirsarman:fix/quote-plugin-root-in-hooks
Open

ozdemirsarman wants to merge 2 commits into
anthropics:mainfrom
ozdemirsarman:fix/quote-plugin-root-in-hooks

Conversation

@ozdemirsarman

Copy link
Copy Markdown

Fixes #78490
Fixes #79143

Two small, independent defects that both leave hookify installed-but-broken. One commit each.


1. Unquoted ${CLAUDE_PLUGIN_ROOT} breaks hooks on paths with spaces (#78490)

hooks.json commands are executed through a shell, so an unquoted
${CLAUDE_PLUGIN_ROOT} word-splits when the plugin lives under a path containing a
space — e.g. a macOS user directory like /Users/Jane Doe/....

For hookify this is not cosmetic. python3 receives /Users/Jane as its script
argument and exits non-zero on every PreToolUse event, so the plugin fails on
every single tool call.

Repro

$ export CLAUDE_PLUGIN_ROOT="/tmp/plugin dir with spaces/hookify"

# before
$ bash -c "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.py" <<< '{"tool_name":"Bash"}'
python3: can't open file '/tmp/plugin': [Errno 2] No such file or directory

# after
$ bash -c "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.py\"" <<< '{"tool_name":"Bash"}'
{}

learning-output-style, explanatory-output-style and ralph-wiggum use the same
unquoted pattern and are fixed alongside. security-guidance already quotes the
variable — this brings the rest in line with it.

2. Example rules never load when copied (#79143)

load_rules() globs for .claude/hookify.*.local.md
(plugins/hookify/core/config_loader.py:210), but the shipped examples are named
dangerous-rm.local.md, console-log-warning.local.md, etc.

Copied into a project as-is — which is what "see examples/" invites — none of them
match the glob, so they silently never run. Nothing errors and nothing is logged; as
far as the plugin is concerned the rule does not exist.

Repro (.claude/ in a project, with the dangerous-rm example, input
{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}})

# dangerous-rm.local.md          -> {}                                  (rule never fires)
# hookify.dangerous-rm.local.md  -> {"systemMessage": "**[block-dangerous-rm]** ..."}

The four examples are renamed to the hookify.*.local.md form, and the
writing-rules skill now states that the prefix is required.


Testing

  • All four hooks.json files still parse as valid JSON.
  • PreToolUse hook executed with CLAUDE_PLUGIN_ROOT set to a path containing a
    space: fails before the change, returns {} after.
  • Example rule copied into .claude/ under both the old and the new filename: only
    the prefixed name is picked up and evaluated.
  • No behaviour change when the plugin path contains no spaces.

Hook commands are executed through a shell, so an unquoted
${CLAUDE_PLUGIN_ROOT} word-splits when the plugin is installed under a path
containing a space (e.g. a macOS user directory like "/Users/Jane Doe/...").

For hookify this is fatal rather than cosmetic: python3 receives "/Users/Jane"
as its script argument and exits non-zero on every PreToolUse event, so the
plugin fails on every single tool call.

  $ export CLAUDE_PLUGIN_ROOT="/tmp/plugin dir with spaces/hookify"
  $ bash -c "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.py"
  python3: can't open file '/tmp/plugin': [Errno 2] No such file or directory

  # after
  $ bash -c "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.py\"" <<< '{"tool_name":"Bash"}'
  {}

learning-output-style, explanatory-output-style and ralph-wiggum use the same
unquoted pattern and are fixed alongside. security-guidance already quotes the
variable; this brings the rest in line with it.

Fixes anthropics#78490
load_rules() globs for `.claude/hookify.*.local.md`, but the shipped examples
are named `dangerous-rm.local.md`, `console-log-warning.local.md`, etc.

Copied into a project as-is - which is what "see examples/" invites - none of
them match the glob, so they silently never run. Nothing errors and nothing is
logged; the rule just does not exist as far as the plugin is concerned.

Renames the four examples to the `hookify.*.local.md` form and states the
prefix requirement in the writing-rules skill.

Fixes anthropics#79143
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