{"record":{"id":"9609e5a9e2bcc934","repo":"different-ai/openwork","slug":"plugin-manifest-not-found","errorCode":"plugin_manifest_not_found","errorMessage":"No .claude-plugin/plugin.json found under ${normalized}/","messagePattern":"No \\.claude-plugin/plugin\\.json found under (.+?)/","errorType":"error_code","errorClass":"ApiError","httpStatus":404,"severity":"error","filePath":"apps/server/src/claude-plugin-bundle.ts","lineNumber":210,"sourceCode":"    } catch (error) {\n      lastError = error;\n    }\n  }\n  if (lastError instanceof Error) throw lastError;\n  throw new ApiError(404, \"plugin_ref_not_found\", \"Could not resolve the requested branch or tag\");\n}\n\n// Find the plugin root: the given subdir, the repo root, or the shallowest\n// directory containing `.claude-plugin/plugin.json`.\nfunction locatePluginRoot(tree: TreeEntry[], dir: string | null): string {\n  const manifestPaths = tree\n    .map((entry) => entry.path)\n    .filter((path) => path === \".claude-plugin/plugin.json\" || path.endsWith(\"/.claude-plugin/plugin.json\"));\n  if (dir) {\n    const normalized = dir.replace(/\\/+$/, \"\");\n    const expected = `${normalized}/.claude-plugin/plugin.json`;\n    if (!manifestPaths.includes(expected)) {\n      throw new ApiError(404, \"plugin_manifest_not_found\", `No .claude-plugin/plugin.json found under ${normalized}/`);\n    }\n    return `${normalized}/`;\n  }\n  if (manifestPaths.length === 0) {\n    throw new ApiError(404, \"plugin_manifest_not_found\", \"No .claude-plugin/plugin.json found in this repository\");\n  }\n  manifestPaths.sort((a, b) => a.split(\"/\").length - b.split(\"/\").length || a.localeCompare(b));\n  const shallowest = manifestPaths[0]!;\n  const root = shallowest.slice(0, shallowest.length - \".claude-plugin/plugin.json\".length);\n  const sameDepth = manifestPaths.filter((path) => path.split(\"/\").length === shallowest.split(\"/\").length);\n  if (sameDepth.length > 1) {\n    const candidates = sameDepth\n      .map((path) => path.slice(0, path.length - \"/.claude-plugin/plugin.json\".length))\n      .join(\", \");\n    throw new ApiError(400, \"plugin_ambiguous\", `Multiple plugins found (${candidates}). Add the plugin directory to the URL, e.g. /tree/main/<dir>.`);\n  }\n  return root;\n}","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/claude-plugin-bundle.ts#L192-L228","documentation":"locatePluginRoot finds the directory containing .claude-plugin/plugin.json within the resolved repo tree. When an explicit dir is given, it requires that exact `<dir>/.claude-plugin/plugin.json` entry to exist in the tree; otherwise it throws this 404 ApiError naming the normalized directory.","triggerScenarios":"Requesting /tree/<ref>/<dir> where dir exists but does not contain .claude-plugin/plugin.json (wrong subdir, manifest at a different level, or manifest filename misspelled).","commonSituations":"Plugin manifest lives at repo root but a subdirectory was passed; plugin restructured so the manifest moved to a nested path; passing the parent directory instead of the plugin root directory; `.claude-plugin` folder omitted when vendoring the plugin.","solutions":["Verify `.claude-plugin/plugin.json` exists under the given dir at that ref on GitHub.","Drop the <dir> segment and let the resolver auto-locate the plugin root (works when the repo has exactly one plugin).","Point the URL at the actual plugin root directory containing .claude-plugin/.","Check the ref: the manifest may exist on another branch/tag but not the one requested."],"exampleFix":"// before\nGET /plugin/tree/main/myplugin/src   // manifest is at myplugin/.claude-plugin/plugin.json\n// after\nGET /plugin/tree/main/myplugin       // point at the plugin root","handlingStrategy":"validation","validationCode":"// verify the manifest path exists at the ref before calling\nconst p = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${dir}/.claude-plugin/plugin.json?ref=${ref}`);\nif (!p.ok) throw new Error(`missing ${dir}/.claude-plugin/plugin.json at ${ref}`);","typeGuard":null,"tryCatchPattern":"try {\n  await installPlugin(url);\n} catch (e) {\n  if (isApiError(e) && e.code === \"plugin_manifest_not_found\") {\n    // retry without the dir segment to auto-locate the plugin root\n  }\n}","preventionTips":["Point the URL at the directory containing .claude-plugin/, not a nested subdir.","Omit <dir> for single-plugin repos.","Verify the file exists at the specific ref, not just on the default branch.","Keep the .claude-plugin directory intact when vendoring."],"tags":["plugin-manifest","github","path","plugin-install"],"backgroundTag":"manifest-file-not-found","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}