claude-code - 💡(How to fix) Fix Plugin hooks: CLAUDE_PLUGIN_ROOT not injected into hook subprocess environment

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Plugin hooks that reference ${CLAUDE_PLUGIN_ROOT} in their command strings fail silently at every hook event because the variable is not set in the bash subprocess environment. Claude Code spawns hooks via bash -c "<command>" without injecting CLAUDE_PLUGIN_ROOT, so the variable expands to an empty string and the path becomes invalid (e.g. "/hooks/session-start.sh").

Error Message

Error shown in UI

SessionStart:startup hook error This appears related to #10936, #17088, #27886, #34713 (false hook error labels). The false-label bug compounds this one: even hooks that exit cleanly are sometimes shown as errors, making it harder to distinguish genuine failures from false positives.

Root Cause

hooks.json command strings like:

"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd session-start"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-start.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/gemini-session-start.js"

When Claude Code spawns bash -c "<command>", CLAUDE_PLUGIN_ROOT is not present in the subprocess environment. Bash expands it to empty string, producing invalid paths.

The superpowers session-start script itself correctly checks [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] to branch output format — so the scripts are written assuming the variable will be set. The issue is in the hook spawning layer.

Fix Action

Workaround

Manually patch each plugin's hooks.json in the cache (~/.claude/plugins/cache/) to add a fallback using bash parameter expansion:

"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/cache/<marketplace>/<plugin>/<version>}\"; \"$_R/hooks/script\""

This is the same pattern used by thedotmack/claude-mem, which works correctly because it includes fallback resolution. This workaround is fragile — it gets overwritten on plugin updates.

Code Example

SessionStart:startup hook error
Failed with non-blocking status code: No stderr output

---

"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd session-start"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-start.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/gemini-session-start.js"

---

"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/cache/<marketplace>/<plugin>/<version>}\"; \"$_R/hooks/script\""
RAW_BUFFERClick to expand / collapse

Summary

Plugin hooks that reference ${CLAUDE_PLUGIN_ROOT} in their command strings fail silently at every hook event because the variable is not set in the bash subprocess environment. Claude Code spawns hooks via bash -c "<command>" without injecting CLAUDE_PLUGIN_ROOT, so the variable expands to an empty string and the path becomes invalid (e.g. "/hooks/session-start.sh").

Environment

  • Claude Code version: 2.1.143
  • OS: Windows 11 Pro 10.0.26200
  • Shell: Git Bash (C:/Program Files/Git/bin/bash.exe)

Affected plugins (confirmed)

  • superpowers@claude-plugins-official (v5.1.0)
  • warp@claude-code-warp (v2.0.0)
  • claude-plugin-dev@functional-claude (v0.6.1)
  • gemini-cli@functional-claude (v0.6.9)

Any plugin whose hooks.json uses ${CLAUDE_PLUGIN_ROOT} in a command field is affected.

Error shown in UI

SessionStart:startup hook error
Failed with non-blocking status code: No stderr output

Repeats once per plugin hook per event (e.g. 4 errors at session start if 4 plugins have SessionStart hooks).

Root cause

hooks.json command strings like:

"command": "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd session-start"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-start.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/gemini-session-start.js"

When Claude Code spawns bash -c "<command>", CLAUDE_PLUGIN_ROOT is not present in the subprocess environment. Bash expands it to empty string, producing invalid paths.

The superpowers session-start script itself correctly checks [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] to branch output format — so the scripts are written assuming the variable will be set. The issue is in the hook spawning layer.

Workaround

Manually patch each plugin's hooks.json in the cache (~/.claude/plugins/cache/) to add a fallback using bash parameter expansion:

"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/cache/<marketplace>/<plugin>/<version>}\"; \"$_R/hooks/script\""

This is the same pattern used by thedotmack/claude-mem, which works correctly because it includes fallback resolution. This workaround is fragile — it gets overwritten on plugin updates.

Expected behavior

CLAUDE_PLUGIN_ROOT should be set to the plugin's resolved cache path (e.g. ~/.claude/plugins/cache/claude-plugins-official/superpowers/5.1.0) in the environment before spawning any hook subprocess registered by that plugin.

Related issues

This appears related to #10936, #17088, #27886, #34713 (false hook error labels). The false-label bug compounds this one: even hooks that exit cleanly are sometimes shown as errors, making it harder to distinguish genuine failures from false positives.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

CLAUDE_PLUGIN_ROOT should be set to the plugin's resolved cache path (e.g. ~/.claude/plugins/cache/claude-plugins-official/superpowers/5.1.0) in the environment before spawning any hook subprocess registered by that plugin.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Plugin hooks: CLAUDE_PLUGIN_ROOT not injected into hook subprocess environment