can1357/oh-my-pi · error · Error

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

Error message

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

What it means

MarketplaceManager.upgradePlugin() requires a fully qualified plugin ID of the form "name@marketplace". Before doing any registry lookup it runs parsePluginId(), and if the ID cannot be split into a plugin name and a marketplace name it throws this error immediately. This is a fail-fast input validation so the upgrade can unambiguously locate the plugin in a specific marketplace.

Source

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

					isNewer = Bun.semver.order(catalogVersion, installed.version) > 0;
				} catch {
					isNewer = catalogVersion !== installed.version;
				}

				if (isNewer) {
					updates.push({ pluginId, scope, from: installed.version, to: catalogVersion });
				}
			}
		}

		return updates;
	}

	// Re-install a specific plugin at the latest catalog version (force-overwrites).
	async upgradePlugin(pluginId: string, scope?: "user" | "project"): Promise<InstalledPluginEntry> {
		const parsed = parsePluginId(pluginId);
		if (!parsed) {
			throw new Error(`Invalid plugin ID: "${pluginId}". Expected "name@marketplace".`);
		}

		const { userEntries, projectEntries } = 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`);
		}

		let resolvedScope: "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 upgrade.`,
				);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Include the marketplace in the ID: call upgradePlugin("my-plugin@official") instead of upgradePlugin("my-plugin").
  2. Check the installed plugin entry (e.g. from listInstalledPlugins()) and use its exact id field, which is already in name@marketplace form.
  3. If the ID is user-supplied, validate it matches /[^@]+@[^@]+/ before calling and prompt or error with a helpful message.
  4. If the marketplace name is unknown, list available marketplaces first and pick the one the plugin was installed from.

Example fix

// before
await manager.upgradePlugin("my-plugin");
// after
await manager.upgradePlugin("my-plugin@official");
Defensive patterns

Strategy: validation

Validate before calling

function isValidPluginId(id: string): boolean {
  const at = id.indexOf("@");
  return at > 0 && at < id.length - 1 && !id.slice(at + 1).includes("@");
}
if (!isValidPluginId(pluginId)) throw new Error(`Plugin ID must be "name@marketplace", got: "${pluginId}"`);
await manager.upgradePlugin(pluginId, scope);

Type guard

function isPluginId(value: string): value is `${string}@${string}` {
  const at = value.indexOf("@");
  return at > 0 && at < value.length - 1;
}

Try / catch

try {
  await manager.upgradePlugin(pluginId, scope);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Invalid plugin ID:")) {
    console.error(`Bad plugin id "${pluginId}" — use name@marketplace form.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling upgradePlugin(pluginId) with a bare plugin name ("my-plugin"), an empty string, a malformed ID like "name@" or "@marketplace", or any string that parsePluginId() cannot split on a valid '@' separator.

Common situations: Users copy a plugin name from a list without its marketplace suffix; scripts pass a CLI argument that was only the plugin name; a shell script strips the @marketplace portion during quoting/escaping; or the ID comes from a stale config where the marketplace was removed.

Related errors


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