{"record":{"id":"6af671647b04c4e7","repo":"can1357/oh-my-pi","slug":"cannot-resolve-relative-source-source-market","errorCode":null,"errorMessage":"Cannot resolve relative source \"${source}\": marketplaceClonePath is required","messagePattern":"Cannot resolve relative source \"(.+?)\": marketplaceClonePath is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts","lineNumber":60,"sourceCode":"\tif (typeof source === \"string\") {\n\t\treturn resolveRelativeSource(source, context);\n\t}\n\n\treturn resolveObjectSource(source, context);\n}\n\n// ── Relative string source (\"./plugins/foo\") ────────────────────────\n\nasync function resolveRelativeSource(\n\tsource: string,\n\tcontext: ResolveContext,\n): Promise<{ dir: string; tempCloneRoot?: string }> {\n\tif (!source.startsWith(\"./\")) {\n\t\tthrow new Error(`Relative plugin source paths must start with \"./\" — got: \"${source}\"`);\n\t}\n\n\tif (!context.marketplaceClonePath) {\n\t\tthrow new Error(`Cannot resolve relative source \"${source}\": marketplaceClonePath is required`);\n\t}\n\n\t// If pluginRoot is set, prepend it to the path segment after \"./\"\n\tconst pluginRoot = context.catalogMetadata?.pluginRoot;\n\tconst relativePath = pluginRoot ? `./${path.join(pluginRoot, source.slice(2))}` : source;\n\n\t// Resolve against marketplace root (not the .claude-plugin/ catalog subdirectory)\n\tconst resolved = path.resolve(context.marketplaceClonePath, relativePath);\n\n\tif (!pathIsWithin(context.marketplaceClonePath, resolved)) {\n\t\tthrow new Error(\n\t\t\t`Plugin source \"${source}\" resolves outside marketplace root (\"${context.marketplaceClonePath}\")`,\n\t\t);\n\t}\n\n\tawait verifyDirExists(resolved, `Plugin source directory does not exist: \"${resolved}\"`);\n\treturn { dir: resolved };\n}","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts#L42-L78","documentation":"Relative plugin sources are resolved against the marketplace's local clone directory. resolveRelativeSource() reads that directory from context.marketplaceClonePath; when the caller did not supply one (no clone exists or the context was built without it), it throws this error because the relative path has nothing to resolve against.","triggerScenarios":"Calling resolvePluginSource() for a \"./...\" source with a ResolveContext lacking marketplaceClonePath — e.g. resolving before the marketplace was cloned/updated, or constructing the context manually and omitting the field after a failed clone.","commonSituations":"Installing a plugin from a marketplace whose clone directory was deleted; a marketplace update failed leaving no local clone; SDK/API usage building ResolveContext by hand without the clone path; running an install before the first marketplace sync.","solutions":["Run the marketplace update/clone step first so marketplaceClonePath exists, then retry the install","Re-add the marketplace to force a fresh clone if the directory was deleted","If embedding the resolver, pass marketplaceClonePath in the ResolveContext","Check for a prior clone failure (network/disk) that left the context incomplete"],"exampleFix":"// before\nconst dir = await resolvePluginSource(\"./plugins/foo\", { catalogMetadata }); // no clonePath\n// after\nconst dir = await resolvePluginSource(\"./plugins/foo\", {\n  marketplaceClonePath: \"~/.omp/marketplaces/community\",\n  catalogMetadata,\n});","handlingStrategy":"validation","validationCode":"function canResolveRelative(ctx) {\n  return Boolean(ctx.marketplaceClonePath);\n}\nif (!canResolveRelative(ctx)) await cloneOrUpdateMarketplace(marketplaceName);","typeGuard":"const hasClonePath = (ctx) => typeof ctx?.marketplaceClonePath === 'string' && ctx.marketplaceClonePath.length > 0;","tryCatchPattern":"try {\n  const dir = await resolvePluginSource(source, ctx);\n} catch (err) {\n  if (err.message.includes('marketplaceClonePath is required')) {\n    await updateMarketplace(marketplaceName); // materialize the clone\n    // rebuild ctx and retry once\n  } else throw err;\n}","preventionTips":["Always clone/update a marketplace before installing its \"./\"-relative plugins","When building ResolveContext manually, include marketplaceClonePath for relative sources","Investigate earlier clone failures (network/disk) that leave the context without a path","Don't delete clone directories while installs may still reference them"],"tags":["plugin","missing-context","resolution"],"backgroundTag":"missing-marketplace-clone","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}