can1357/oh-my-pi · error · Error
Invalid path: ${localPath} resolves outside working director
Error message
Invalid path: ${localPath} resolves outside working directory What it means
linkPlugin links a local directory into the plugins tree for development. To prevent path traversal it resolves the supplied localPath against cwd and requires the result to be equal to or inside the resolved cwd. This error is thrown when the resolved absolute path escapes the working directory.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/installer.ts:150
manifest: pkg.omp || pkg.pi || { version: pkg.version },
enabledFeatures: null,
enabled: true,
});
}
}
return plugins;
}
export async function linkPlugin(localPath: string): Promise<void> {
const cwd = getProjectDir();
const absolutePath = path.resolve(cwd, localPath);
// Validate that resolved path is within cwd to prevent path traversal
const normalizedCwd = path.resolve(cwd);
const normalizedPath = path.resolve(absolutePath);
if (!normalizedPath.startsWith(`${normalizedCwd}/`) && normalizedPath !== normalizedCwd) {
throw new Error(`Invalid path: ${localPath} resolves outside working directory`);
}
// Validate package.json exists
const pkgFile = Bun.file(path.join(absolutePath, "package.json"));
if (!(await pkgFile.exists())) {
throw new Error(`package.json not found at ${absolutePath}`);
}
let pkg: { name?: string };
try {
pkg = await pkgFile.json();
} catch (err) {
throw new Error(`Invalid package.json at ${absolutePath}: ${err}`);
}
if (!pkg.name || typeof pkg.name !== "string") {
throw new Error("package.json must have a valid name field");
}View on GitHub (pinned to 9690622007)
Solutions
- Move or symlink the plugin directory inside the current working directory and link that path
- Pass a path relative to cwd that does not traverse upward with `..`
- If the plugin legitimately lives elsewhere, copy it into the project rather than linking
- Use the project's supported mechanism for external plugins (install from npm/git) instead of linkPlugin
Example fix
// before (escapes cwd)
await linkPlugin("../../shared/my-plugin");
// after (path inside cwd)
await linkPlugin("plugins-local/my-plugin"); Defensive patterns
Strategy: validation
Validate before calling
const abs = path.resolve(cwd, localPath);
const inside = abs === path.resolve(cwd) || abs.startsWith(path.resolve(cwd) + path.sep);
if (!inside) throw new Error(`refusing to link outside cwd: ${localPath}`);
if (!(await Bun.file(path.join(abs, "package.json")).exists())) {
throw new Error(`not a package root (no package.json): ${abs}`);
} Try / catch
try {
await linkPlugin(localPath);
} catch (err) {
if (err instanceof Error && err.message.includes("resolves outside working directory")) {
// copy the plugin into the project or use a supported install mechanism
}
throw err;
} Prevention
- Keep plugin source directories inside the project working directory
- Never pass absolute paths or `..`-climbing relative paths to linkPlugin
- Resolve and assert the path stays inside cwd in your own tooling before calling
When it happens
Trigger: linkPlugin(localPath, cwd) is called with a path containing `..` segments, an absolute path in a different tree, or a symlink-resolving parent that normalizes outside cwd — e.g. linkPlugin("../../shared-plugin") or linkPlugin("/opt/plugins/foo", cwd).
Common situations: Keeping plugin source outside the project (a monorepo sibling directory) and passing a relative path that climbs out; absolute paths pasted from elsewhere; CI working directories that differ from local ones so a previously-valid relative path now escapes.
Related errors
- Destination paths cannot contain parent traversal or NUL byt
- Shared-folder destination escapes its configured root
- Absolute paths are not allowed in ${scheme}:// URLs: ${rawPa
- Path traversal (..) is not allowed in ${scheme}:// URLs: ${r
- Unsafe #{scheme}:// path (absolute or traversal): #{path}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/fd436499a0a7c77f.
Report an issue: GitHub.