[FEATURE] Parameterized plugin-root placeholder `${CLAUDE_PLUGIN_ROOT:<plugin-name>}` in settings.json hooks

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 30, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

${CLAUDE_PLUGIN_ROOT} only resolves inside a plugin's own hooks/hooks.json, where the owning plugin provides the context. In settings.json / settings.local.json hooks there is no such context, so the placeholder cannot be used — yet that is exactly where hooks must live when a project wants selective configuration (e.g. registering only some of a plugin's hook matchers, or different matchers per project).

Today the only workaround is hardcoding the resolved cache path:

"command": "& \"$env:USERPROFILE\\.claude\\plugins\\cache\\my-marketplace\\my-plugin\\1.1.0\\dist\\tool.exe\"",
"shell": "powershell"

This is fragile in three ways:

  • Version-pinned — every plugin update moves the cache directory and silently breaks the hook.
  • Marketplace-name-coupled — the path embeds the marketplace identifier, an implementation detail that can be renamed, misleading, or differ across machines.
  • Shell-dependent — env-var expansion syntax differs per hook shell (%VAR% expands nowhere, $VAR only in Git Bash, $env:VAR only in PowerShell), forcing shell pinning just to build a path.

Proposed Solution

Support a parameterized form of the existing placeholder, resolved by Claude Code itself (like ${CLAUDE_PROJECT_DIR}) before any shell sees the command:

"command": "\"${CLAUDE_PLUGIN_ROOT:my-plugin}/dist/tool.exe\""

Resolution: the installation root of the named, enabled plugin — whatever marketplace it came from and whatever version is currently installed. Ambiguity (same plugin name from two marketplaces) could be resolved with an optional qualified form ${CLAUDE_PLUGIN_ROOT:marketplace/plugin}; an unknown or disabled plugin name should fail the hook visibly rather than silently.

Benefits

  • Project settings can compose plugin-shipped executables/scripts into custom hook selections without duplicating the plugin's all-or-nothing hooks.json.
  • Hook registrations survive plugin updates — no re-init after every version bump.
  • Paths are portable across machines and shells — no user-profile layout, marketplace naming, or shell-specific expansion baked into checked-in settings.
  • Consistent with the existing placeholder model (${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}) — same expansion point, just with an explicit plugin argument. A matching ${CLAUDE_PLUGIN_DATA:<plugin-name>} would round the feature out.

Alternative Solutions

_No response_

Priority

High - Significant impact on productivity

Feature Category

Configuration and settings

Use Case Example

A hook plugin ships one native exe serving several independent fixes (file-format enforcement, encoding recode, search-result filtering), dispatching on the hook payload. Which features are active is controlled purely by which matchers a project registers — inherently a per-project settings.json concern. The plugin's own hooks.json can't express that selectivity, and hardcoded cache paths break on every release.

Additional Context

_No response_

View original on GitHub ↗