can1357/oh-my-pi · error · Error
Plugin source directory does not exist: "${resolved}"
Error message
Plugin source directory does not exist: "${resolved}" What it means
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.
Source
Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/source-resolver.ts:152
}
return { dir: subdirPath, tempCloneRoot: cloneDir };
}
case "npm":
throw new Error("npm plugin sources are not yet supported. Use git-based sources instead.");
default:
throw new Error(`Unknown plugin source type: "${(source as { source: string }).source}"`);
}
}
// ── Helpers ─────────────────────────────────────────────────────────
async function verifyDirExists(dirPath: string, errorMessage: string): Promise<void> {
try {
const stat = await fs.stat(dirPath);
if (!stat.isDirectory()) {
throw new Error(errorMessage);
}
} catch (err) {
if (isEnoent(err)) {
throw new Error(errorMessage);
}
throw err;
}
}
View on GitHub (pinned to 9690622007)
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.
Example fix
// before (.claude-plugin/marketplace.json)
{ "name": "formatter", "source": "./tools/formatter-plugin" } // directory was renamed
// after (after confirming actual layout in the clone)
{ "name": "formatter", "source": "./plugins/formatter" } Defensive patterns
Strategy: try-catch
Validate before calling
import * as fs from "node:fs/promises";
async function pluginDirExists(dirPath: string): Promise<boolean> {
try {
return (await fs.stat(dirPath)).isDirectory();
} catch {
return false;
}
}
// pre-check resolved candidate paths before calling resolvePluginSource, when you can compute them Try / catch
try {
const { dir } = await resolvePluginSource(entry, context);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.startsWith("Plugin source directory does not exist:") || msg.includes("does not exist in cloned repository")) {
// the resolved path from the message tells you exactly what to fix in the catalog entry
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- failed to create {} via template {}: No such file or directo
- target {0}: Not a directory
- target directory {0}: Not a directory
- Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePa
- Plugin source "${source}" resolves outside marketplace root
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/08d32e4a21f181cb.
Report an issue: GitHub.