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
- Remove and re-add the marketplace using its git URL so the full tree is cloned: removeMarketplace then addMarketplace with the repo URL
- Pick the same plugin from a git- or local-sourced marketplace that publishes it with a resolvable source
- 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
- Always add marketplaces by their git repo URL, not raw marketplace.json URLs, when plugins use relative sources
- Document per-marketplace whether plugins use relative paths and require git source
- Prefer git-sourced marketplaces in CI/setup scripts
- If authoring a marketplace, use git/remote source entries instead of relative string paths for URL-distributed catalogs
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
- Plugin "${name}" not found in marketplace "${marketplace}"
- Plugin "${pluginId}" is already installed. Use force option
- Plugin "${entry.name}" lspServers path escapes the plugin di
- Plugin "${entry.name}" dapAdapters path escapes the plugin d
- Invalid plugin ID format: "${pluginId}". Expected "name@mark
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/01e16dc832f385f8.
Report an issue: GitHub.