{"record":{"id":"b9db106f6ff21db2","repo":"can1357/oh-my-pi","slug":"relative-plugin-source-paths-must-start-with","errorCode":null,"errorMessage":"Relative plugin source paths must start with \"./\" — got: \"${source}\"","messagePattern":"Relative plugin source paths must start with \"\\./\" — got: \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts","lineNumber":56,"sourceCode":"\tcontext: ResolveContext,\n): Promise<{ dir: string; tempCloneRoot?: string }> {\n\tconst { source } = entry;\n\n\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}","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Prefix the source with \"./\" in the catalog entry: \"./plugins/foo\"","Ensure the path is relative to the marketplace root (pluginRoot is applied automatically) — not absolute or \"..\"-based","Re-run /marketplace update after fixing the catalog so the corrected entry is fetched","If authoring a catalog, validate every plugin source starts with \"./\""],"exampleFix":"// before (catalog entry)\n{\"name\": \"foo\", \"source\": \"plugins/foo\"}\n// after\n{\"name\": \"foo\", \"source\": \"./plugins/foo\"}","handlingStrategy":"validation","validationCode":"function isRelativePluginSource(source) {\n  return typeof source === 'string' && source.startsWith('./') && !source.slice(2).includes('..');\n}\nif (!isRelativePluginSource(entry.source)) throw new Error(`source must start with \"./\": ${entry.source}`);","typeGuard":"const isDotRelativeSource = (v) => typeof v === 'string' && v.startsWith('./');","tryCatchPattern":"try {\n  const dir = await resolvePluginSource(source, ctx);\n} catch (err) {\n  if (err.message.startsWith('Relative plugin source paths must start with')) {\n    console.error(`Fix the catalog entry: \"${source}\" → \"./${source}\"`);\n  } else throw err;\n}","preventionTips":["When authoring catalogs, always write plugin sources as \"./path\" relative to the marketplace root","Add a lint/check step over catalog JSON validating the \"./\" prefix","Don't copy path conventions from other plugin ecosystems that allow bare relative paths","Avoid \"..\" segments; use pluginRoot metadata for nested layouts"],"tags":["plugin","validation","path-format"],"backgroundTag":"invalid-relative-source-path","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}