Plugin SessionStart hooks fail — ${CLAUDE_PLUGIN_ROOT} not expanded for bash commands
Bug Description
Plugin SessionStart hooks that use ${CLAUDE_PLUGIN_ROOT} in bash commands fail on fresh session start. The environment variable is not being expanded, resulting in the hook trying to execute from the filesystem root.
Environment
- Claude Code: 2.1.84
- OS: macOS (Darwin 25.4.0)
- Plugins affected:
explanatory-output-style@claude-plugins-official,learning-output-style@claude-plugins-official
Steps to Reproduce
- Install
explanatory-output-styleandlearning-output-stylefrom claude-plugins-official - Start a fresh Claude Code session (
claude) - Observe
SessionStart:startup hook errormessages
Error
Both plugins' hooks.json contain:
{
"hooks": [{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks-handlers/session-start.sh"
}]
}
When executed, produces:
bash: /hooks-handlers/session-start.sh: No such file or directory
${CLAUDE_PLUGIN_ROOT} resolves to empty, so the path becomes /hooks-handlers/session-start.sh instead of the full plugin cache path.
Other plugins work fine
The superpowers plugin also uses ${CLAUDE_PLUGIN_ROOT} in its SessionStart hook but works correctly because it uses bash -c '"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd" session-start' (quoted expansion).
The context-mode plugin uses node ${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart.mjs and also works fine.
Workaround
Disabled both affected plugins. The issue appears specific to how ${CLAUDE_PLUGIN_ROOT} is expanded (or not) for bash shell commands in certain plugin hook configurations.
Expected Behavior
${CLAUDE_PLUGIN_ROOT} should be expanded to the plugin's cache path before the hook command is executed, regardless of whether the command uses node, bash, or other shell execution.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗