can1357/oh-my-pi · error · Error

Invalid package name: ${name}

Error message

Invalid package name: ${name}

What it means

validatePackageName checks the plugin package name against VALID_PACKAGE_NAME before any install/uninstall command runs, throwing 'Invalid package name: <name>' on failure. It is an injection guard for names later passed to spawned bun/npm processes.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/installer.ts:17

import * as fs from "node:fs/promises";
import * as path from "node:path";
import { getAgentDir, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
import { extractPackageName } from "./parser";
import type { InstalledPlugin } from "./types";

const PLUGINS_DIR = path.join(getAgentDir(), "plugins");

// Valid npm package name pattern (scoped and unscoped)
const VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[a-z0-9-._^~>=<]+)?$/i;

/**
 * Validate package name to prevent command injection
 */
function validatePackageName(name: string): void {
	if (!VALID_PACKAGE_NAME.test(name)) {
		throw new Error(`Invalid package name: ${name}`);
	}
	// Extra safety: no shell metacharacters
	if (/[;&|`$(){}[\]<>\\]/.test(name)) {
		throw new Error(`Invalid characters in package name: ${name}`);
	}
}

/**
 * Ensure the plugins directory exists
 */
async function ensurePluginsDir(): Promise<void> {
	await fs.mkdir(PLUGINS_DIR, { recursive: true });
	await fs.mkdir(path.join(PLUGINS_DIR, "node_modules"), { recursive: true });
}

export async function installPlugin(packageName: string): Promise<InstalledPlugin> {
	// Validate package name to prevent command injection
	validatePackageName(packageName);

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the bare package name (e.g. 'omp-plugin-git'), no version or URL.
  2. Trim whitespace from names sourced from config/CLI args before calling.
  3. For scoped packages, check whether the validator's pattern accepts the @scope/ prefix; strip or normalize accordingly.
  4. Handle the throw and surface a clear user-facing validation message.

Example fix

// before
await installPlugin("my-plugin@^1.0.0")
// after
await installPlugin("my-plugin") // version goes elsewhere, not in the name
Defensive patterns

Strategy: validation

Validate before calling

const VALID = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/; // mirror the shim's expectation
if (!VALID.test(name) || /[;&|`$(){}[\]<>\\]/.test(name)) {
  throw new Error(`Refusing invalid plugin name: ${name}`);
}
await installPlugin(name);

Type guard

function isValidPluginName(name: string): boolean {
  return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) && !/[;&|`$(){}[\]<>\\]/.test(name);
}

Try / catch

try {
  await uninstallPlugin(name);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Invalid package name:")) {
    // report validation failure to the caller; do not mutate-and-retry
  } else throw err;
}

Prevention

When it happens

Trigger: installPlugin(name) or uninstallPlugin(name) with a name that doesn't match the allowed pattern — empty string, scoped names if the regex disallows them (@scope/pkg), names with version specifiers, whitespace, or URL-ish inputs.

Common situations: Passing 'my-plugin@1.2.0' or a git URL to uninstallPlugin; accidental whitespace from config parsing; programmatically constructed names with null bytes or shell fragments.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3cd7aa43e82e2bba. Report an issue: GitHub.