{"record":{"id":"d187863510b2de2b","repo":"jackwener/OpenCLI","slug":"updated-sub-plugin-pluginname-is-invalid","errorCode":null,"errorMessage":"Updated sub-plugin \"${pluginName}\" is invalid:\n- ${validation.errors.join('\n- ')}","messagePattern":"Updated sub-plugin \"(.+?)\" is invalid:\n- (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/plugin.ts","lineNumber":995,"sourceCode":"    name: string;\n    lockEntry: LockEntry;\n    manifestEntry: NonNullable<PluginManifest['plugins']>[string];\n  }> = [];\n\n  for (const [pluginName, entry] of Object.entries(lock)) {\n    if (entry.source.kind !== 'monorepo' || entry.source.repoName !== monoName) continue;\n    const manifestEntry = manifest.plugins?.[pluginName];\n    if (!manifestEntry || manifestEntry.disabled) {\n      throw new Error(`Installed sub-plugin \"${pluginName}\" no longer exists in ${cloneUrl}`);\n    }\n    if (manifestEntry.opencli && !checkCompatibility(manifestEntry.opencli)) {\n      throw new Error(`Sub-plugin \"${pluginName}\" requires opencli ${manifestEntry.opencli}`);\n    }\n\n    const subDir = resolveRepoContainedPath(tmpCloneDir, manifestEntry.path);\n    const validation = validatePluginStructure(subDir);\n    if (!validation.valid) {\n      throw new Error(`Updated sub-plugin \"${pluginName}\" is invalid:\\n- ${validation.errors.join('\\n- ')}`);\n    }\n    updatedPlugins.push({ name: pluginName, lockEntry: entry, manifestEntry });\n  }\n\n  return updatedPlugins;\n}\n\nfunction updateMonorepoLockEntries(\n  lock: Record<string, LockEntry>,\n  plugins: Array<{\n    name: string;\n    lockEntry: LockEntry;\n    manifestEntry: NonNullable<PluginManifest['plugins']>[string];\n  }>,\n  cloneUrl: string,\n  monoName: string,\n  commitHash: string | undefined,\n): void {","sourceCodeStart":977,"sourceCodeEnd":1013,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/plugin.ts#L977-L1013","documentation":"As the final gate of a monorepo update, opencli runs validatePluginStructure against each updated sub-plugin's directory inside the freshly cloned repo (resolved via resolveRepoContainedPath). If the new checkout fails structural validation, the update aborts and lists each validation error. This prevents replacing a working installed sub-plugin with a broken upstream revision.","triggerScenarios":"Updating a monorepo when the upstream revision has a broken sub-plugin: missing manifest or entry files under manifestEntry.path, wrong path in the manifest, or incomplete clone contents in tmpCloneDir.","commonSituations":"Upstream published a broken commit; manifestEntry.path points to a renamed/moved package directory; shallow/partial clone missing files; path separators or case mismatches on different OSes.","solutions":["Read the bullet-listed errors and fix upstream (or use a repo revision where the sub-plugin is valid).","Pin the update to the last known-good commit/tag of the monorepo.","Verify manifestEntry.path in the upstream manifest matches the actual package directory containing the manifest.","Retry after clearing temp clone state in case of an incomplete clone."],"exampleFix":"// before (manifest path stale after rename)\n\"lint\": { \"path\": \"packages/lint\" }   // packages/lint no longer exists\n// after\n\"lint\": { \"path\": \"packages/eslint-plugin\" }","handlingStrategy":"validation","validationCode":"const subDir = path.join(tmpCloneDir, manifestEntry.path);\nif (!fs.existsSync(path.join(subDir, 'opencli.json'))) {\n  throw new Error(`Updated sub-plugin at ${subDir} has no manifest; upstream revision is broken`);\n}","typeGuard":"function dirHasManifest(base: string, relPath: string): boolean {\n  try { return fs.statSync(path.join(base, relPath, 'opencli.json')).isFile(); } catch { return false; }\n}","tryCatchPattern":"try {\n  await opencli.plugin.updateMonorepo(monoName);\n} catch (e) {\n  if (e.message.includes('is invalid')) {\n    console.error('Pin the monorepo to a known-good revision or fix upstream, then retry:', e.message);\n  } else throw e;\n}","preventionTips":["Validate sub-plugin paths in the monorepo manifest after renames.","Update to tagged releases instead of arbitrary commits.","Run structure validation on upstream packages before pulling updates."],"tags":["plugin","monorepo","update","validation","structure"],"backgroundTag":"plugin-structure-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}