From 5c2850bdd10463cdd39490ad8ef1305833970373 Mon Sep 17 00:00:00 2001 From: cdeust Date: Tue, 11 Aug 2026 00:02:16 +0200 Subject: [PATCH] fix(plugin): refuse an unset CLAUDE_PLUGIN_ROOT instead of guessing $PWD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All nine hook commands resolved their root as ${CLAUDE_PLUGIN_ROOT:-$PWD}. That fallback cannot work: the launcher lives inside the plugin, never in the user's working directory, so when the variable is unset the hook runs python3 against a path that is wrong by construction and reports "can't open file '/scripts/launcher.py'" — an error that names the wrong cause and sends the reader looking at their own project. Measured 2026-08-10 from an unrelated repository: every Cortex hook failed that way, silently, until the cause was traced back here. A fallback that guarantees a wrong path is not a fallback; it converts a clear configuration error into a misleading runtime one. The hooks now refuse and name the missing variable. Verified both ways on the final state: unset -> "CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root", exit 1; set -> identical behaviour to before the change (it reaches the launcher). JSON re-parsed after the edit. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01263uv1QqR8TVzw2jXYUrXn --- .claude-plugin/plugin.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 09ea55bd..878231c4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -52,7 +52,7 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.session_start'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.session_start'", "timeout": 30 } ] @@ -63,7 +63,7 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.auto_recall'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.auto_recall'", "timeout": 5 } ] @@ -74,22 +74,22 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.post_tool_capture'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.post_tool_capture'", "timeout": 10 }, { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.preemptive_context'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.preemptive_context'", "timeout": 5 }, { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.pipeline_impact_bump'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.pipeline_impact_bump'", "timeout": 5 }, { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.post_commit_reindex'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.post_commit_reindex'", "timeout": 10 } ] @@ -100,7 +100,7 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.session_lifecycle'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.session_lifecycle'", "timeout": 30 } ] @@ -112,7 +112,7 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.compaction_checkpoint'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.compaction_checkpoint'", "timeout": 10 } ] @@ -123,7 +123,7 @@ "hooks": [ { "type": "command", - "command": "bash -c 'PY=$(command -v python3 || command -v python) && ROOT=\"${CLAUDE_PLUGIN_ROOT:-$PWD}\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.agent_briefing'", + "command": "bash -c 'PY=$(command -v python3 || command -v python) && if [ -z \"${CLAUDE_PLUGIN_ROOT:-}\" ]; then echo \"cortex hook: CLAUDE_PLUGIN_ROOT is unset; refusing to guess the plugin root\" >&2; exit 1; fi; ROOT=\"$CLAUDE_PLUGIN_ROOT\" && \"$PY\" \"$ROOT/scripts/launcher.py\" mcp_server.hooks.agent_briefing'", "timeout": 5 } ]