{"record":{"id":"fa1efa25dd507434","repo":"can1357/oh-my-pi","slug":"unknown-plugin-source-type-source-as-source","errorCode":null,"errorMessage":"Unknown plugin source type: \"${(source as { source: string }).source}\"","messagePattern":"Unknown plugin source type: \"(.+?)\\)\\.source\\}\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts","lineNumber":142,"sourceCode":"\t\t\tconst subdirPath = path.resolve(cloneDir, source.path);\n\t\t\tif (!pathIsWithin(cloneDir, subdirPath)) {\n\t\t\t\tawait fs.rm(cloneDir, { recursive: true, force: true });\n\t\t\t\tthrow new Error(`git-subdir path \"${source.path}\" escapes the cloned repository`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait verifyDirExists(subdirPath, `git-subdir path \"${source.path}\" does not exist in cloned repository`);\n\t\t\t} catch (err) {\n\t\t\t\tawait fs.rm(cloneDir, { recursive: true, force: true });\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\treturn { dir: subdirPath, tempCloneRoot: cloneDir };\n\t\t}\n\n\t\tcase \"npm\":\n\t\t\tthrow new Error(\"npm plugin sources are not yet supported. Use git-based sources instead.\");\n\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown plugin source type: \"${(source as { source: string }).source}\"`);\n\t}\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────\n\nasync function verifyDirExists(dirPath: string, errorMessage: string): Promise<void> {\n\ttry {\n\t\tconst stat = await fs.stat(dirPath);\n\t\tif (!stat.isDirectory()) {\n\t\t\tthrow new Error(errorMessage);\n\t\t}\n\t} catch (err) {\n\t\tif (isEnoent(err)) {\n\t\t\tthrow new Error(errorMessage);\n\t\t}\n\t\tthrow err;\n\t}\n}","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts#L124-L160","documentation":"The default branch of resolveObjectSource's switch fires when a plugin entry's source object declares a source type the resolver does not recognize — anything other than \"url\", \"github\", \"git-subdir\", or \"npm\". It reports the unrecognized value verbatim so the offending catalog entry can be found and corrected. Unlike the npm case, this usually signals a typo, an invalid/foreign catalog format, or catalog JSON not conforming to the PluginSource schema.","triggerScenarios":"resolvePluginSource is called with an object source whose .source string matches no case, e.g. { \"source\": \"git\" } instead of \"git-subdir\", { \"source\": \"local\", \"path\": ... }, { \"source\": \"NPM\" } (wrong case), or an entirely foreign shape from another tool's marketplace format; also malformed entries where the discriminator field holds a typo or the JSON was hand-edited.","commonSituations":"Typos in marketplace.json (\"gitub\", \"gitsubdir\", \"Git-Hub\"); copying plugin entries from Claude Code or other ecosystems whose source vocabularies differ; schema drift after a resolver update renamed or dropped a source type; build tooling emitting a wrong discriminator value.","solutions":["Open the marketplace catalog and change the entry's source field to one of the supported values: \"url\", \"github\", \"git-subdir\" (string relative paths like \"./plugins/foo\" are also valid).","Check spelling and case — the match is exact, so \"Github\" or \"git-sub\" fail; correct to lowercase exact literals.","If the entry came from another tool's catalog format, translate it to this resolver's PluginSource schema rather than copying it verbatim.","Validate the catalog JSON against the PluginSource type/schema before loading to catch the bad discriminator early."],"exampleFix":"// before\n{ \"name\": \"my-plugin\", \"source\": \"git\", \"url\": \"https://github.com/owner/repo.git\" }\n\n// after\n{ \"name\": \"my-plugin\", \"source\": \"url\", \"url\": \"https://github.com/owner/repo.git\" }","handlingStrategy":"validation","validationCode":"const KNOWN_SOURCE_TYPES = new Set([\"url\", \"github\", \"git-subdir\", \"npm\"]);\n\nfunction hasKnownSourceType(entry: { source: unknown }): boolean {\n  if (typeof entry.source === \"string\") return entry.source.startsWith(\"./\");\n  if (typeof entry.source === \"object\" && entry.source !== null) {\n    const t = (entry.source as { source?: unknown }).source;\n    return typeof t === \"string\" && KNOWN_SOURCE_TYPES.has(t);\n  }\n  return false;\n}\n// reject/warn on entries failing this check before calling resolvePluginSource","typeGuard":"function hasRecognizedSource(source: unknown): boolean {\n  if (typeof source === \"string\") return source.startsWith(\"./\");\n  if (typeof source !== \"object\" || source === null) return false;\n  const t = (source as { source?: unknown }).source;\n  return t === \"url\" || t === \"github\" || t === \"git-subdir\" || t === \"npm\";\n}","tryCatchPattern":"try {\n  const { dir } = await resolvePluginSource(entry, context);\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  const m = msg.match(/^Unknown plugin source type: \"(.*)\"$/);\n  if (m) {\n    // report the exact bad type (m[1]) and the offending catalog entry; skip or abort\n  } else throw err;\n}","preventionTips":["Use exact lowercase literals: \"url\", \"github\", \"git-subdir\" (or a \"./\"-prefixed string)","Run marketplace.json through a schema validator with an enum on the source discriminator before install","Translate foreign catalog formats to this resolver's schema rather than copying entries verbatim","Re-validate catalogs after upgrading, in case the source-type vocabulary changed"],"tags":["configuration","schema-validation","plugin-marketplace","typo"],"backgroundTag":"unknown-enum-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}