claude-code - 💡(How to fix) Fix Plugin subagents can't resolve ${CLAUDE_PLUGIN_ROOT}/${CLAUDE_PROJECT_DIR} — no way to read plugin-bundled files from a subagent

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…

Fix Action

Fix / Workaround

A subagent dispatched from a plugin cannot resolve ${CLAUDE_PLUGIN_ROOT} or ${CLAUDE_PROJECT_DIR} — the tokens reach the subagent's tool calls as literal strings. Since a subagent runs with the project as its cwd and has no handle on the plugin's install directory, there is no way for a plugin's subagent to read files bundled inside that plugin (shared standards docs, per-action contracts, prompt partials, etc.).

  1. A plugin with a bundled file kernel/STANDARDS.md and one subagent myplugin:worker.
  2. In a session with the plugin loaded, dispatch myplugin:worker and instruct it to Read ${CLAUDE_PLUGIN_ROOT}/kernel/STANDARDS.md.
  3. The Read receives the literal ${CLAUDE_PLUGIN_ROOT}/... and fails.
  4. Contrast: the identical path resolves in a SessionStart hook and in a slash command.

There's no clean way to give a plugin's agents a shared contract / base definition / "inheritance." Every workaround has a real cost:

RAW_BUFFERClick to expand / collapse

What happened

A subagent dispatched from a plugin cannot resolve ${CLAUDE_PLUGIN_ROOT} or ${CLAUDE_PROJECT_DIR} — the tokens reach the subagent's tool calls as literal strings. Since a subagent runs with the project as its cwd and has no handle on the plugin's install directory, there is no way for a plugin's subagent to read files bundled inside that plugin (shared standards docs, per-action contracts, prompt partials, etc.).

The surprising part is the asymmetry — the same variables resolve everywhere except subagents (all verified on Claude Code 2.1.166 via headless --output-format stream-json runs):

  • SessionStart hooks resolve themcat "${CLAUDE_PLUGIN_ROOT}/kernel/BOOTSTRAP.md" works.
  • Slash commands resolve ${CLAUDE_PLUGIN_ROOT} for their own file reads — a command reads ${CLAUDE_PLUGIN_ROOT}/docs/CATALOG.md fine.
  • Subagents do NOT — handing a subagent's Read the path ${CLAUDE_PLUGIN_ROOT}/kernel/KERNEL.md fails with File does not exist. Note: your current working directory is <project>.
  • A command's Bash env also lacks itecho "$CLAUDE_PLUGIN_ROOT" prints empty, so an orchestrator command can't even resolve-then-pass the path to the agents it spawns without brittle filesystem hunting (which in our case surfaced a stale orphaned plugin cache under ~/.claude/plugins/cache/.../0.1.0/).

Repro

  1. A plugin with a bundled file kernel/STANDARDS.md and one subagent myplugin:worker.
  2. In a session with the plugin loaded, dispatch myplugin:worker and instruct it to Read ${CLAUDE_PLUGIN_ROOT}/kernel/STANDARDS.md.
  3. The Read receives the literal ${CLAUDE_PLUGIN_ROOT}/... and fails.
  4. Contrast: the identical path resolves in a SessionStart hook and in a slash command.

Why it matters

There's no clean way to give a plugin's agents a shared contract / base definition / "inheritance." Every workaround has a real cost:

  • Paste into the spawn prompt (orchestrator reads the shared doc and injects its text into every agent prompt) — runtime copy rather than reference, bloats every spawn, couples delivery to one command.
  • Duplicate inline in each agent definition — defeats DRY, drifts.
  • Stage into the project filesystem (a SessionStart hook copies the plugin file into <project>/.actionflows/ so subagents can read it by relative path) — works, but the plugin then writes identical, non-project-specific content into every project's tree purely as a delivery vehicle.

Requests (in priority order)

  1. Expose ${CLAUDE_PLUGIN_ROOT} (and ${CLAUDE_PROJECT_DIR}) to subagents — substitute them in subagent prompt text and/or make them resolvable by the subagent's tools, the same way hooks and commands already get them.
  2. Native agent inheritance / includes — let a subagent definition reference shared partials bundled in the plugin (e.g. an extends:/include: that composes the system prompt from a base file at load time). This is the capability actually needed.
  3. At minimum, a documented, stable way for a command to obtain the resolved absolute ${CLAUDE_PLUGIN_ROOT}, so an orchestrator can pass it to the subagents it spawns.

Environment

  • Claude Code 2.1.166
  • Context: building a multi-agent orchestration plugin whose subagents need a shared, DRY "essential standards" contract.

Happy to share the stream-json transcripts demonstrating each of the four resolution behaviors above.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING