can1357/oh-my-pi · error · Error

Relative plugin source paths must start with "./" — got: "${

Error message

Relative plugin source paths must start with "./" — got: "${source}"

What it means

resolveRelativeSource() handles plugin sources that are paths relative to the marketplace clone (e.g. "./plugins/foo"). It strictly requires the "./" prefix so a bare relative path can't be confused with a plugin name, git URL, or other source kind routed by resolvePluginSource. Any relative-looking source without the prefix throws this error.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts:56

	context: ResolveContext,
): Promise<{ dir: string; tempCloneRoot?: string }> {
	const { source } = entry;

	if (typeof source === "string") {
		return resolveRelativeSource(source, context);
	}

	return resolveObjectSource(source, context);
}

// ── Relative string source ("./plugins/foo") ────────────────────────

async function resolveRelativeSource(
	source: string,
	context: ResolveContext,
): Promise<{ dir: string; tempCloneRoot?: string }> {
	if (!source.startsWith("./")) {
		throw new Error(`Relative plugin source paths must start with "./" — got: "${source}"`);
	}

	if (!context.marketplaceClonePath) {
		throw new Error(`Cannot resolve relative source "${source}": marketplaceClonePath is required`);
	}

	// If pluginRoot is set, prepend it to the path segment after "./"
	const pluginRoot = context.catalogMetadata?.pluginRoot;
	const relativePath = pluginRoot ? `./${path.join(pluginRoot, source.slice(2))}` : source;

	// Resolve against marketplace root (not the .claude-plugin/ catalog subdirectory)
	const resolved = path.resolve(context.marketplaceClonePath, relativePath);

	if (!pathIsWithin(context.marketplaceClonePath, resolved)) {
		throw new Error(
			`Plugin source "${source}" resolves outside marketplace root ("${context.marketplaceClonePath}")`,
		);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Prefix the source with "./" in the catalog entry: "./plugins/foo"
  2. Ensure the path is relative to the marketplace root (pluginRoot is applied automatically) — not absolute or ".."-based
  3. Re-run /marketplace update after fixing the catalog so the corrected entry is fetched
  4. If authoring a catalog, validate every plugin source starts with "./"

Example fix

// before (catalog entry)
{"name": "foo", "source": "plugins/foo"}
// after
{"name": "foo", "source": "./plugins/foo"}
Defensive patterns

Strategy: validation

Validate before calling

function isRelativePluginSource(source) {
  return typeof source === 'string' && source.startsWith('./') && !source.slice(2).includes('..');
}
if (!isRelativePluginSource(entry.source)) throw new Error(`source must start with "./": ${entry.source}`);

Type guard

const isDotRelativeSource = (v) => typeof v === 'string' && v.startsWith('./');

Try / catch

try {
  const dir = await resolvePluginSource(source, ctx);
} catch (err) {
  if (err.message.startsWith('Relative plugin source paths must start with')) {
    console.error(`Fix the catalog entry: "${source}" → "./${source}"`);
  } else throw err;
}

Prevention

When it happens

Trigger: A marketplace catalog entry declares a plugin source like "plugins/foo" or "../shared/foo" instead of "./plugins/foo"; hand-written catalog JSON omitting the "./"; programmatic resolvePluginSource calls passing a raw relative path.

Common situations: Authoring a custom marketplace catalog and forgetting the "./" convention; copying path strings from docs or other plugin systems that allow bare relative paths; paths containing ".." segments that also violate the prefix rule.

Related errors


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