/doctor false-positive: warns 'Missing environment variables: CLAUDE_PLUGIN_ROOT' for plugin MCP servers
Summary
/doctor warns that plugin .mcp.json entries are missing the CLAUDE_PLUGIN_ROOT environment variable, even though the MCP servers are healthy and working. The warning is a lint-time false positive: /doctor checks the current shell env for every ${VAR} referenced in .mcp.json, but CLAUDE_PLUGIN_ROOT is injected by the plugin launcher at subprocess-spawn time — it's never present in the shell env that /doctor inspects.
Steps to reproduce
- Install any plugin whose bundled
.mcp.jsonuses${CLAUDE_PLUGIN_ROOT}to point at a server entry-point. Minimal example:
``json``
{
"mcpServers": {
"example": {
"command": "uv",
"args": [
"run",
"--project",
"${CLAUDE_PLUGIN_ROOT}",
"fastmcp",
"run",
"${CLAUDE_PLUGIN_ROOT}/server.py"
]
}
}
}
- Enable the plugin. The MCP server starts fine —
${CLAUDE_PLUGIN_ROOT}resolves to the cached plugin path andserver.pyloads normally. - Run
/doctor. - Observe:
````
Warning mcpServers.example: Missing environment variables: CLAUDE_PLUGIN_ROOT
Expected behaviour
/doctor should recognise CLAUDE_PLUGIN_ROOT (and CLAUDE_PLUGIN_DATA) as plugin-system-managed variables that are guaranteed to be injected at MCP launch, and not flag them as missing. The warning should only surface for variables that are genuinely the user's responsibility to set.
Actual behaviour
The warning fires on every /doctor run and can't be silenced without hardcoding the plugin cache path in .mcp.json (which breaks portability and defeats the purpose of the placeholder).
Workarounds considered
- Hardcode the absolute cache path — kills the warning but non-portable, breaks on plugin updates (cache path includes the version).
- Export
CLAUDE_PLUGIN_ROOT=/anythingin the parent shell — would satisfy the lint, but plugin-launcher env-inheritance semantics aren't documented. If the launcher merges parent env with injected values instead of strictly overriding, the dummy could leak into the MCP subprocess and break path resolution. - Ignore the warning — what users end up doing.
Docs reference
The plugin reference docs state that CLAUDE_PLUGIN_ROOT and CLAUDE_PLUGIN_DATA are substituted inline in MCP / LSP server configs, hook commands, skill content, and agent content. The substitution works correctly at runtime; only /doctor's lint step is unaware of it.
Related
- #41003 —
/doctorvalidates individual MCP config files instead of merged result (similar class: context-blind linting). - #49983 — flags intentional cross-platform MCP config as warnings (alarm fatigue from
/doctor). - #47789 —
headersHelperdoes not expand${CLAUDE_PLUGIN_ROOT}(different: that one is a genuine runtime bug, not a lint false positive).
Environment
- Claude Code: v2.1.112
- Platform: macOS 25.4.0 (Darwin, zsh)
- Plugin installed via local marketplace (
claude plugin install <name>@<marketplace> --scope user)
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗