can1357/oh-my-pi · error · Error
Invalid package name in package.json: ${pkg.name}
Error message
Invalid package name in package.json: ${pkg.name} What it means
The parsed name is checked for path-traversal payloads: `..`, `/`, or `\`. Scoped npm names (starting with `@`) are allowed but only with exactly one slash. This error is thrown when the manifest's name would be unsafe to use as a directory name under plugins/node_modules.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/installer.ts:174
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");
}
// Validate package name to prevent path traversal via pkg.name
if (pkg.name.includes("..") || pkg.name.includes("/") || pkg.name.includes("\\")) {
// Exception: scoped packages have one slash
if (!pkg.name.startsWith("@") || (pkg.name.match(/\//g) || []).length !== 1) {
throw new Error(`Invalid package name in package.json: ${pkg.name}`);
}
}
await ensurePluginsDir();
// Create symlink in plugins/node_modules
const linkPath = path.join(PLUGINS_DIR, "node_modules", pkg.name);
// For scoped packages, ensure the scope directory exists
if (pkg.name.startsWith("@")) {
const scopeDir = path.join(PLUGINS_DIR, "node_modules", pkg.name.split("/")[0]);
await fs.mkdir(scopeDir, { recursive: true });
}
// Remove existing if present
try {
const stats = await fs.lstat(linkPath);
if (stats.isSymbolicLink() || stats.isDirectory()) {View on GitHub (pinned to 9690622007)
Solutions
- Rename the package in package.json to a valid npm name (lowercase, no `..`/`\`, at most one slash only for `@scope/name` scoped form)
- Move nested path components out of the name field into the actual directory structure
- Re-run linkPlugin with the corrected manifest
Example fix
// before
{ "name": "@scope/team/plugin" }
// after
{ "name": "@scope-plugin" } or { "name": "@scope/plugin" } Defensive patterns
Strategy: validation
Validate before calling
const name = (await Bun.file(path.join(dir, "package.json")).json()).name;
const unsafe = name.includes("..") || name.includes("\\") ||
(name.includes("/") && !(name.startsWith("@") && (name.match(/\//g) || []).length === 1));
if (unsafe) throw new Error(`unsafe package name: ${name}`); Type guard
function isSafePkgName(name: string): boolean {
if (name.includes("..") || name.includes("\\")) return false;
if (!name.includes("/")) return true;
return name.startsWith("@") && (name.match(/\//g) || []).length === 1;
} Try / catch
try {
await linkPlugin(dir);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Invalid package name in package.json")) {
// rename the package in its package.json
}
throw err;
} Prevention
- Follow npm naming rules: `@scope/pkg` with exactly one slash, or unscoped plain names
- Never embed paths, `..`, or backslashes in the name field
- Validate manifests from third parties before linking them
When it happens
Trigger: linkPlugin(localPath, cwd) reads a package.json whose name is `../../evil`, `a\\b`, contains slashes in non-scoped form, or a scoped name with more than one slash (e.g. `@scope/deep/name`).
Common situations: Malicious or typo'd manifests with traversal names; deeply scoped private registries using multi-slash names, which npm itself forbids; Windows-style names pasted into package.json.
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/8854ac877ee908c7.
Report an issue: GitHub.