EveryInc/compound-engineering-plugin · error · Error
Invalid ${label}: ${entry}. Paths must stay within the plugi
Error message
Invalid ${label}: ${entry}. Paths must stay within the plugin root. What it means
`resolveWithinRoot()` resolves a declared path entry against the plugin root and enforces that the result stays inside that root. If the entry escapes (via `..`, absolute paths, or symlinks resolving outside), the parser throws this error to block path traversal from plugin manifests.
Source
Thrown at src/parsers/claude.ts:269
function unwrapMcpServers(raw: Record<string, unknown>): Record<string, ClaudeMcpServer> {
if (raw.mcpServers && typeof raw.mcpServers === "object") {
return raw.mcpServers as Record<string, ClaudeMcpServer>
}
return raw as Record<string, ClaudeMcpServer>
}
function mergeMcpConfigs(configs: Record<string, ClaudeMcpServer>[]): Record<string, ClaudeMcpServer> {
return configs.reduce((acc, config) => ({ ...acc, ...config }), {})
}
function resolveWithinRoot(root: string, entry: string, label: string): string {
const resolvedRoot = path.resolve(root)
const resolvedPath = path.resolve(root, entry)
if (resolvedPath === resolvedRoot || resolvedPath.startsWith(resolvedRoot + path.sep)) {
return resolvedPath
}
throw new Error(`Invalid ${label}: ${entry}. Paths must stay within the plugin root.`)
}
View on GitHub (pinned to c9c10f8c75)
Solutions
- Change the manifest entry to a path relative to the plugin root (e.g. `skills/ce-plan` instead of `../ce-plan`)
- Copy shared files into the plugin rather than referencing them via traversal
- Remove any absolute paths from plugin.json and re-declare them as relative paths
- If you own the plugin, restructure so all referenced assets live under the plugin root
Example fix
// before (plugin.json)
{ "skills": { "source": "../shared-skills" } }
// after
{ "skills": { "source": "skills" } } Defensive patterns
Strategy: validation
Validate before calling
import path from "node:path"
function staysWithinRoot(root: string, entry: string): boolean {
const r = path.resolve(root)
const p = path.resolve(root, entry)
return p === r || p.startsWith(r + path.sep)
}
if (!staysWithinRoot(pluginRoot, declaredPath)) throw new Error(`Refusing: ${declaredPath} escapes the plugin root`) Type guard
function isWithinRoot(root: string, entry: string): boolean {
const resolved = path.resolve(root, entry)
return resolved === path.resolve(root) || resolved.startsWith(path.resolve(root) + path.sep)
} Try / catch
try {
const plugin = await loadClaudePlugin(pluginRoot)
} catch (error) {
if (String(error).includes("Paths must stay within the plugin root")) {
console.error("Fix the escaping path in the plugin manifest; copy shared files into the plugin instead")
} else throw error
} Prevention
- Never use `..` or absolute paths in plugin manifest path entries
- Copy shared assets into each skill/plugin directory (self-contained units)
- Review manifests with a path-containment check in CI
When it happens
Trigger: Calling the Claude parser on a plugin whose `.claude-plugin/plugin.json` (or component dir config) declares entries like `"../shared/skills"`, an absolute path such as `/etc/…`, or `"../../outside"` for skills/agents/commands directories.
Common situations: Authoring a plugin that tries to share files across skill/plugin boundaries with `..` traversal (explicitly banned by this repo's conventions); converting a third-party plugin with absolute paths from another machine; moving a plugin checkout without fixing absolute paths in its manifest.
Related errors
- Cleanup currently supports only the compound-engineering plu
- Unknown cleanup target: ${target}. Use one of: ${cleanupTarg
- Unknown permissions mode: ${permissions}
- Unknown target: ${targetName}
- Unknown permissions mode: ${permissions}
AI-assisted analysis of EveryInc/compound-engineering-plugin@c9c10f8c75 (2026-08-31).
Data as JSON: /api/errors/870d9764007b1e6e.
Report an issue: GitHub.