can1357/oh-my-pi · error · Error

Invalid plugin ID format: "${pluginId}". Expected "name@mark

Error message

Invalid plugin ID format: "${pluginId}". Expected "name@marketplace".

What it means

uninstallPlugin requires the plugin to be identified as "name@marketplace". It runs parsePluginId(pluginId) and throws when the string does not match that format. This prevents ambiguous deletions where a bare name could match plugins from multiple marketplaces.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/manager.ts:449

				}
			} catch {
				// Missing or invalid — try next
			}
		}

		// 3. Git SHA from source definition
		if (typeof entry.source === "object" && "sha" in entry.source && entry.source.sha) {
			return entry.source.sha.slice(0, 7);
		}

		return "0.0.0";
	}

	/** Validates and removes a marketplace plugin, or only validates when `dryRun` is set. */
	async uninstallPlugin(pluginId: string, scope?: "user" | "project", options?: { dryRun?: boolean }): Promise<void> {
		const parsed = parsePluginId(pluginId);
		if (!parsed) {
			throw new Error(`Invalid plugin ID format: "${pluginId}". Expected "name@marketplace".`);
		}

		const { userEntries, projectEntries, userReg, projectReg } = await this.#findInBothRegistries(pluginId);
		const inUser = userEntries && userEntries.length > 0;
		const inProject = projectEntries && projectEntries.length > 0;

		if (!inUser && !inProject) {
			throw new Error(`Plugin "${pluginId}" is not installed`);
		}

		// Disambiguation: if installed in both scopes and no explicit scope, require one.
		let targetScope: "user" | "project";
		if (inUser && inProject) {
			if (!scope) {
				throw new Error(
					`Plugin "${pluginId}" is installed in both user and project scope. Use --scope user or --scope project to specify which to remove.`,
				);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the full id as "name@marketplace", e.g. uninstallPlugin("formatter@community")
  2. Look up the exact installed id in the installed-plugins registry (or via list) and copy it verbatim
  3. If building the id programmatically, use buildPluginId(name, marketplace)

Example fix

// before
await manager.uninstallPlugin("formatter");
// after
await manager.uninstallPlugin("formatter@community");
Defensive patterns

Strategy: validation

Validate before calling

function isValidPluginId(id: string): boolean {
  const at = id.lastIndexOf("@");
  return at > 0 && at < id.length - 1 && !id.slice(0, at).includes("@") === false || at > 0; // keep simple:
}
// simpler:
if (!/^[^@\s]+@[^@\s]+$/.test(pluginId)) throw new Error(`need name@marketplace, got ${pluginId}`);
await manager.uninstallPlugin(pluginId);

Type guard

function isPluginId(v: string): boolean {
  const m = /^([^@\s]+)@([^@\s]+)$/.exec(v);
  return !!m;
}

Try / catch

try {
  await manager.uninstallPlugin(rawArg);
} catch (err) {
  if (err instanceof Error && err.message.includes("Invalid plugin ID format")) {
    console.error(`Usage: omp plugin uninstall <name>@<marketplace> (got "${rawArg}")`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling uninstallPlugin with an id missing the @marketplace suffix, containing extra @ separators the parser rejects, or being empty/whitespace — e.g. uninstallPlugin("formatter") instead of uninstallPlugin("formatter@community").

Common situations: Users typing just the plugin name on the CLI; scripts passing a name copied from a display label rather than the registry id; shell quoting stripping an @ token; stale docs predating the name@marketplace convention.

Related errors


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