CLAUDE_PLUGIN_ROOT inconsistent between hooks/skills and agent environment for local marketplace plugins
Bug Description
When a plugin is installed from a local marketplace (claude plugin install cat@cat), CLAUDE_PLUGIN_ROOT is set to two different values depending on the context:
| Context | CLAUDE_PLUGIN_ROOT |
|---|---|
| Hooks (e.g., session-start.sh) | /workspace/plugin (source directory) |
| Skill preprocessor directives (${CLAUDE_PLUGIN_ROOT}) | /workspace/plugin (source directory) |
| Agent Bash environment (echo $CLAUDE_PLUGIN_ROOT) | ~/.config/claude/plugins/cache/cat/cat/2.1 (cache/install directory) |
Expected Behavior
CLAUDE_PLUGIN_ROOT should consistently point to the installation directory (the installPath from installed_plugins.json) in all contexts — hooks, skill preprocessor directives, and the agent's shell environment.
Actual Behavior
Hooks and skill preprocessor directives receive the source directory from the marketplace definition, while the agent's Bash environment receives the cache/install directory. This causes skills that reference ${CLAUDE_PLUGIN_ROOT}/client/bin/... to fail with "no such file or directory" because compiled binaries exist in the cache but not in the source tree.
Steps to Reproduce
- Create a local marketplace with
sourcepointing to a plugin directory:
``json``
// .claude-plugin/marketplace.json
{
"name": "my-marketplace",
"owner": { "name": "example" },
"plugins": [
{
"name": "my-plugin",
"source": "./plugin",
"description": "Example plugin"
}
]
}
- Install the plugin:
claude plugin install my-marketplace@my-plugin - Verify
installed_plugins.jsonshowsinstallPathpointing to the cache directory - In a hook script, log
$CLAUDE_PLUGIN_ROOT— it shows the source directory (e.g.,/workspace/plugin) - In the agent's Bash tool, run
echo $CLAUDE_PLUGIN_ROOT— it shows the cache directory
Evidence
Debug output from a session-start hook:
script_dir=/workspace/plugin/hooks
BASH_SOURCE[0]=/workspace/plugin/hooks/session-start.sh
CLAUDE_PLUGIN_ROOT (raw)=/workspace/plugin
installed_plugins.json entry:
{
"installPath": "/home/node/.config/claude/plugins/cache/cat/cat/2.1",
"version": "2.1"
}
Agent Bash environment:
CLAUDE_PLUGIN_ROOT=/home/node/.config/claude/plugins/cache/cat/cat/2.1
Impact
Any skill using ${CLAUDE_PLUGIN_ROOT} in preprocessor directives to locate compiled binaries or other non-source artifacts fails when the plugin is installed from a local marketplace, because the preprocessor resolves the variable to the source directory where those artifacts don't exist.
Environment
- Claude Code CLI (Version 2.1.83)
- Linux (WSL2)
This is a regression. Version 2.1.81 works as expected.
6 Comments
This issue is still reproducible in version 2.1.85
Root Cause Analysis
The regression was introduced in v2.1.83. The corresponding v2.1.83 changelog entry is:
This added a new "cache-only" session-start loader function (minified as
Sc_in v2.1.83) that doesn't exist in v2.1.81. The new function has a bug in its string-source branch.Code comparison (from
stringson the compiled binaries)v2.1.81 — only has the full loader. For string-source plugins, it resolves from the marketplace source directory, then copies to the versioned cache and uses the cache path:
v2.1.83 — added a separate
Sc_function for session-start that skips the copy. For string-source plugins, it resolves from the marketplace directory and uses it directly, ignoring theinstallPathparameter entirely:The
installPathparameter is available but only used in theelsebranch (object-source plugins like github/git/npm). String-source plugins ("source": "./plugin") take the wrong branch.Impact
The source directory path flows into:
${CLAUDE_PLUGIN_ROOT}→ source dir)CLAUDE_PLUGIN_ROOTenv var → source dir)${CLAUDE_PLUGIN_ROOT}in SKILL.md → source dir)CLAUDE_PLUGIN_ROOTin stdio env → source dir)The agent's own Bash environment is the only context that correctly uses
installPath.See also #39156 which reports the same underlying bug from the skill loader perspective.
Workaround: set it yourself at the top of each hook:
The plugin root should point to the root of the actual plugin (e.g.
/home/somebody/.claude/plugins/cache/some-marketplace/some-plugin/1.0.0/. You can obviously still hack this but it's a bit more complicated than what you suggest.This issue seems to be fixed in 2.1.94:
It works locally for me.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.