{"record":{"id":"08d32e4a21f181cb","repo":"can1357/oh-my-pi","slug":"plugin-source-directory-does-not-exist-resolve","errorCode":null,"errorMessage":"Plugin source directory does not exist: \"${resolved}\"","messagePattern":"Plugin source directory does not exist: \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts","lineNumber":152,"sourceCode":"\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}\n","sourceCodeStart":134,"sourceCodeEnd":161,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts#L134-L161","documentation":"verifyDirExists is the final disk check performed after a plugin source resolves to a candidate directory. It stats the path and throws the supplied message when the path does not exist (ENOENT) or exists but is not a directory; other stat errors (permission denied, etc.) are rethrown unchanged. For relative sources and git-subdir sources this message names the fully resolved path, so it means: the source resolution succeeded, but nothing usable (a directory) is at that location.","triggerScenarios":"Called from resolveRelativeSource (source: 1470 context) when the resolved path inside the marketplace clone is missing or is a file, and from resolveObjectSource's git-subdir branch when source.path does not exist in the cloned repo or is a file, not a directory. Triggering inputs: typo'd relative path in the catalog; plugin directory renamed/deleted/moved upstream; wrong ref/sha pinned to an older layout; entry pointing at a single file instead of a plugin directory; incomplete marketplace clone (shallow/partial checkout missing submodules or sparse-checkout exclusions).","commonSituations":"Catalog lists \"./plugins/foo\" but the repo's folder is \"./plugin/foo\" or was renamed in a refactor; marketplace.json updated upstream while the local clone is stale (or vice versa); pinned sha predates the directory's creation; plugin root set via catalogMetadata.pluginRoot prepended incorrectly so the joined path misses; git-subdir path pointing at a README file rather than the plugin folder.","solutions":["Verify the exact directory exists at the resolved path shown in the message; fix the catalog source path (or pluginRoot) to match the actual layout, e.g. \"./plugins/foo\" vs \"./foo\".","Update the marketplace clone (git pull / re-clone) or correct the pinned ref/sha so it points at a revision containing the directory.","If the entry points at a file, point it at the plugin's directory instead — the resolver requires a directory containing the plugin.","Check for sparse-checkout/submodule settings on the clone that exclude the path, and ensure read permissions on the directory."],"exampleFix":"// before (.claude-plugin/marketplace.json)\n{ \"name\": \"formatter\", \"source\": \"./tools/formatter-plugin\" }  // directory was renamed\n\n// after (after confirming actual layout in the clone)\n{ \"name\": \"formatter\", \"source\": \"./plugins/formatter\" }","handlingStrategy":"try-catch","validationCode":"import * as fs from \"node:fs/promises\";\n\nasync function pluginDirExists(dirPath: string): Promise<boolean> {\n  try {\n    return (await fs.stat(dirPath)).isDirectory();\n  } catch {\n    return false;\n  }\n}\n// pre-check resolved candidate paths before calling resolvePluginSource, when you can compute them","typeGuard":null,"tryCatchPattern":"try {\n  const { dir } = await resolvePluginSource(entry, context);\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  if (msg.startsWith(\"Plugin source directory does not exist:\") || msg.includes(\"does not exist in cloned repository\")) {\n    // the resolved path from the message tells you exactly what to fix in the catalog entry\n  } else throw err;\n}","preventionTips":["After editing marketplace.json, verify each relative source path exists in the clone at the pinned ref/sha","Keep the marketplace clone up to date with the catalog layout; re-clone after upstream renames","Point entries at directories, never at individual files","Check pinned shas/refs predate directory creation when pinning old revisions","Watch for sparse-checkout or missing submodules that hide directories from an otherwise-valid clone"],"tags":["missing-directory","plugin-marketplace","configuration","filesystem"],"backgroundTag":"path-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}