can1357/oh-my-pi · error · Error

Plugin "${name}" uses a relative source path but marketplace

Error message

Plugin "${name}" uses a relative source path but marketplace "${marketplace}" was added via URL. Relative sources require a git or local marketplace. Re-add the marketplace using its git URL.

What it means

Plugins can declare their source as a relative string path (e.g. "./plugins/foo") that is resolved against the marketplace root. URL-sourced marketplaces only cache marketplace.json — not the full plugin tree — so a relative source has nothing to resolve against, and installPlugin throws rather than guessing. Git- or local-sourced marketplaces have a full clone/tree and work fine.

Source

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

		}

		// 4. Resolve source path.
		// marketplaceClonePath is the marketplace root — the directory containing .claude-plugin/
		// catalogPath is <marketplacesCacheDir>/<name>/marketplace.json, so the root is two levels up.
		// For local sources the content was fetched from a local path; the stored catalog is a copy
		// under marketplacesCacheDir. We need the original source root for resolving relative paths.
		// Use: path.dirname(catalogPath) is <cacheDir>/<name>/, and that IS the stored copy root,
		// so `path.resolve(mktEntry.catalogPath, "../..")` = parent of <name>/ inside cacheDir
		// which is wrong for local sources. Instead, derive from the stored catalog directory:
		// stored at: <marketplacesCacheDir>/<catalogName>/marketplace.json
		// The marketplace root for local sources should be the actual local path, but we only have
		// sourceUri. For local sources, use path.resolve of sourceUri; for others use the cache dir.
		const marketplaceClonePath = this.#resolveMarketplaceRoot(mktEntry);

		// URL-sourced marketplaces only cache marketplace.json, not the full plugin tree.
		// Relative string sources ("./plugins/foo") cannot be resolved against the cache dir.
		if (mktEntry.sourceType === "url" && typeof pluginEntry.source === "string") {
			throw new Error(
				`Plugin "${name}" uses a relative source path but marketplace "${marketplace}" was added via URL. ` +
					`Relative sources require a git or local marketplace. Re-add the marketplace using its git URL.`,
			);
		}

		const { dir: sourcePath, tempCloneRoot } = await resolvePluginSource(pluginEntry, {
			marketplaceClonePath,
			catalogMetadata: catalog.metadata,
			tmpDir: os.tmpdir(),
		});

		// 5. Determine version: catalog entry > plugin manifest > git SHA > fallback
		let version!: string;
		let cachePath!: string;
		try {
			version = await this.#resolvePluginVersion(pluginEntry, sourcePath);
			cachePath = await cachePlugin(sourcePath, this.#opts.pluginsCacheDir, marketplace, name, version);
			await this.#writeEmbeddedLspConfig(pluginEntry, cachePath);

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove and re-add the marketplace using its git URL so the full tree is cloned: removeMarketplace then addMarketplace with the repo URL
  2. Pick the same plugin from a git- or local-sourced marketplace that publishes it with a resolvable source
  3. If you control the marketplace, switch the plugin entry to an absolute/remote source (git URL) instead of a relative path

Example fix

// before
await manager.addMarketplace({ source: "https://github.com/acme/plugins/raw/main/.claude-plugin/marketplace.json" });
await manager.installPlugin("lint-rules", "acme"); // relative "./plugins/lint-rules" cannot resolve
// after
await manager.addMarketplace({ source: "https://github.com/acme/plugins.git", name: "acme" });
await manager.installPlugin("lint-rules", "acme");
Defensive patterns

Strategy: validation

Validate before calling

// Check the marketplace source type before installing relative-path plugins
const reg = await readMarketplacesRegistry(manager.marketplacesRegistryPath);
const entry = getMarketplaceEntry(reg, marketplace);
if (entry?.sourceType === "url") {
  throw new Error(`Marketplace ${marketplace} is URL-sourced; re-add via git URL to install tree plugins`);
}

Try / catch

try {
  await manager.installPlugin(name, marketplace);
} catch (err) {
  if (err instanceof Error && err.message.includes("relative source path")) {
    await manager.removeMarketplace(marketplace);
    await manager.addMarketplace({ source: GIT_URLS[marketplace], name: marketplace });
    await manager.installPlugin(name, marketplace);
  } else throw err;
}

Prevention

When it happens

Trigger: installPlugin(name, marketplace) where mktEntry.sourceType === "url" and pluginEntry.source is a string (relative path) — i.e. installing a tree-relative plugin from a marketplace that was added by raw URL to its marketplace.json.

Common situations: Adding a marketplace by pasting the raw github.com URL of marketplace.json instead of its git repo URL; a marketplace migrated from git to a URL source; sharing an install command that worked for a git-cloned marketplace.

Related errors


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