can1357/oh-my-pi · error · Error

Marketplace "${catalog.name}" already exists

Error message

Marketplace "${catalog.name}" already exists

What it means

addMarketplace fetches a new marketplace source and refuses to register it if a marketplace with the same catalog name already exists in the registry. It cleans up any temporary clone before throwing. Registry entries are keyed by catalog name, so duplicates would be ambiguous.

Source

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

		const extra = this.#opts.projectInstalledRegistryPath
			? ([this.#opts.projectInstalledRegistryPath] as readonly string[])
			: undefined;
		this.#opts.clearPluginRootsCache?.(extra);
	}

	// ── Marketplace lifecycle ─────────────────────────────────────────────────

	async addMarketplace(source: string): Promise<MarketplaceRegistryEntry> {
		const reg = await readMarketplacesRegistry(this.#opts.marketplacesRegistryPath);
		const existingNames = new Set(reg.marketplaces.map(m => m.name));

		const { catalog, clonePath } = await fetchMarketplace(source, this.#opts.marketplacesCacheDir);

		if (existingNames.has(catalog.name)) {
			if (clonePath) {
				await fs.rm(clonePath, { recursive: true, force: true }).catch(() => {});
			}
			throw new Error(`Marketplace "${catalog.name}" already exists`);
		}

		// Promote the temp clone to its final cache location now that we know it's not a duplicate.
		if (clonePath) {
			await promoteCloneToCache(clonePath, this.#opts.marketplacesCacheDir, catalog.name);
		}

		const sourceType = classifySource(source);
		const normalizedSource = sourceType === "local" ? path.resolve(expandTilde(source)) : source;

		const catalogPath = path.resolve(
			expandTilde(path.join(this.#opts.marketplacesCacheDir, catalog.name, "marketplace.json")),
		);

		// Persist the fetched catalog so subsequent reads don't require re-fetching.
		await Bun.write(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`);

		const now = new Date().toISOString();

View on GitHub (pinned to 9690622007)

Solutions

  1. If you want to refresh the existing marketplace, use updateMarketplace(name) instead of addMarketplace.
  2. If the sources are genuinely different, rename the `name` field in one catalog so they are distinct.
  3. Remove the existing marketplace first (removeMarketplace) and then re-add the desired source.

Example fix

// before: duplicate add
await manager.addMarketplace("https://example.com/marketplace.json"); // name "community" exists
// after: update instead
await manager.updateMarketplace("community");
Defensive patterns

Strategy: try-catch

Validate before calling

const reg = JSON.parse(await Bun.file(registryPath).text());
// after fetching/parsing the new catalog locally:
if (reg.marketplaces.some(m => m.name === catalogName)) {
  console.warn(`Marketplace "${catalogName}" already registered — call updateMarketplace instead`);
}

Try / catch

try {
  await manager.addMarketplace(source);
} catch (err) {
  if ((err as Error).message.includes("already exists")) {
    // either treat as success (idempotent add) or route to updateMarketplace
  } else throw err;
}

Prevention

When it happens

Trigger: MarketplaceManager.addMarketplace(source) called when the fetched catalog's `name` matches an entry already in the marketplaces registry — e.g. re-adding the same marketplace, or two different sources whose catalogs declare the same name.

Common situations: Running an add command twice; two forks of the same marketplace both named "community"; a curated marketplace and a personal mirror sharing a name; the built-in marketplace already registered and the user adding a source with the same name.

Related errors


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