{"record":{"id":"3f41cb7eda6d46ed","repo":"jackwener/OpenCLI","slug":"monorepo-manifest-missing-or-invalid-at-reporoot","errorCode":null,"errorMessage":"Monorepo manifest missing or invalid at ${repoRoot}","messagePattern":"Monorepo manifest missing or invalid at (.+?)","errorType":"validation","errorClass":"PluginError","httpStatus":null,"severity":"error","filePath":"src/plugin.ts","lineNumber":860,"sourceCode":"  writeLockFile(lock);\n}\n\n/** Install sub-plugins from a monorepo. */\nfunction installMonorepo(\n  cloneDir: string,\n  cloneUrl: string,\n  repoName: string,\n  manifest: PluginManifest,\n  subPlugin?: string,\n): string[] {\n  const monoreposDir = getMonoreposDir();\n  const repoDir = path.join(monoreposDir, repoName);\n  const repoAlreadyInstalled = fs.existsSync(repoDir);\n  const repoRoot = repoAlreadyInstalled ? repoDir : cloneDir;\n  const effectiveManifest = repoAlreadyInstalled ? readPluginManifest(repoDir) : manifest;\n\n  if (!effectiveManifest || !isMonorepo(effectiveManifest)) {\n    throw new PluginError(`Monorepo manifest missing or invalid at ${repoRoot}`);\n  }\n\n  let pluginsToInstall = getEnabledPlugins(effectiveManifest);\n\n  // If a specific sub-plugin was requested, filter to just that one\n  if (subPlugin) {\n    pluginsToInstall = pluginsToInstall.filter((p) => p.name === subPlugin);\n    if (pluginsToInstall.length === 0) {\n      // Check if it exists but is disabled\n      const disabled = effectiveManifest.plugins?.[subPlugin];\n      if (disabled) {\n        throw new PluginError(`Sub-plugin \"${subPlugin}\" is disabled in the manifest.`);\n      }\n      throw new PluginError(\n        `Sub-plugin \"${subPlugin}\" not found in monorepo. Available: ${Object.keys(effectiveManifest.plugins ?? {}).join(', ')}`\n      );\n    }\n  }","sourceCodeStart":842,"sourceCodeEnd":878,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/plugin.ts#L842-L878","documentation":"When installing/updating from a monorepo plugin source, opencli reads the manifest either from the already-installed repo directory or from a freshly cloned copy and validates it with isMonorepo. If the manifest is missing or does not have the expected monorepo shape (e.g. a plugins map), it throws. This ensures the repo actually is a multi-plugin monorepo before iterating its sub-plugins.","triggerScenarios":"Installing a plugin by monorepo name/repo URL where the cloned repo has no plugin manifest at its root, the manifest is malformed JSON, or it lacks the required monorepo fields (isMonorepo returns false) — including when reusing an existing installed repoDir whose manifest has become unreadable.","commonSituations":"Wrong repo URL (a normal single-plugin repo or a non-plugin repo); manifest file renamed/deleted upstream; clone of an empty or wrong branch; corrupted prior install directory whose manifest can no longer be read.","solutions":["Verify the repo URL/name points at a plugin monorepo containing a valid manifest with a plugins section.","Delete the existing installed repo directory under the monorepos/plugins dir so a fresh clone and manifest read occurs.","Check the upstream repo: restore or fix the root plugin manifest, and confirm you cloned the intended branch/tag.","If installing a specific sub-plugin, confirm the sub-plugin name exists in the monorepo manifest's plugins map."],"exampleFix":"// before: manifest missing plugins map\n{ \"name\": \"my-mono-repo\" }\n// after\n{ \"name\": \"my-mono-repo\", \"plugins\": { \"core\": { \"path\": \"packages/core\" } } }","handlingStrategy":"validation","validationCode":"const manifestPath = path.join(cloneDir, 'opencli.json');\nif (!fs.existsSync(manifestPath)) throw new Error(`No manifest at ${cloneDir}`);\nconst manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\nif (!manifest.plugins || typeof manifest.plugins !== 'object') {\n  throw new Error(`${cloneDir} is not a plugin monorepo`);\n}","typeGuard":"function isMonorepoManifest(m: unknown): m is { plugins: Record<string, unknown> } {\n  return !!m && typeof m === 'object' && 'plugins' in m && typeof (m as any).plugins === 'object';\n}","tryCatchPattern":"try {\n  await opencli.plugin.installFromMonorepo(repo, subPlugin);\n} catch (e) {\n  if (e.message.includes('Monorepo manifest missing or invalid')) {\n    console.error(`Verify ${repo} is a plugin monorepo with a valid root manifest.`);\n  } else throw e;\n}","preventionTips":["Verify the repo URL hosts a plugin monorepo before installing.","Inspect the repo's root manifest (plugins map) in the browser first.","Remove stale installed repo directories so fresh clones are used."],"tags":["plugin","monorepo","manifest","git"],"backgroundTag":"invalid-manifest","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}