{"record":{"id":"7b2a3a38a6d903e4","repo":"agalwood/Motrix","slug":"plugin-manifest-invalid-7b2a3a","errorCode":"PLUGIN_MANIFEST_INVALID","errorMessage":"plugin.update.builtin_no_package","messagePattern":"plugin\\.update\\.builtin_no_package","errorType":"error_code","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/update/builtin-updater.ts","lineNumber":51,"sourceCode":"interface StagedUpdate {\n  pluginId: string\n  stagingDir: string\n}\n\nconst OVERLAY_META = '_overlay.json'\n\nexport class BuiltinUpdater {\n  private readonly pending = new Map<string, StagedUpdate>()\n\n  constructor(private readonly opts: BuiltinUpdaterOptions) {}\n\n  async stage(\n    entry: RegistryPluginDTO,\n    effective: PluginManifest\n  ): Promise<BuiltinStageResult> {\n    const pkg = entry.package\n    if (!pkg) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        'plugin.update.builtin_no_package'\n      )\n    }\n    if (!pkg.signature) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        'plugin.update.builtin_no_signature'\n      )\n    }\n    if (!semverGt(entry.version, effective.version)) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        'plugin.update.builtin_not_newer'\n      )\n    }\n\n    const bytes = await fetchVerifiedPackageBytes(entry, this.opts.fetchImpl)","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/update/builtin-updater.ts#L33-L69","documentation":"Thrown by BuiltinUpdater.stage when entry.package is falsy for a builtin plugin update. Builtin updates are the hot-update path for plugins shipped in the app bundle; they require a signed package descriptor (url/size/sha256/signature). A builtin update entry without a package means the registry listed a new version but supplied no artifact to install, so staging aborts before touching the overlay.","triggerScenarios":"builtinUpdater.stage(entry, effective) where `entry.package` is undefined/null. The entry came from the registry (RegistryClient) describing a builtin plugin version, but the package blob descriptor was omitted.","commonSituations":"Builtin update was published to the registry index before the package blob/signature upload completed. Registry file partially regenerated and the package section dropped. Schema drift where the builtin entry shape changed but the producer was not updated.","solutions":["Publish the package artifact and signature for the builtin version, then regenerate the registry entry so it includes package { url, size, sha256, signature }.","Skip the update on the client until the registry entry is complete (the running builtin stays at its current version).","Audit the registry publishing pipeline to fail when a builtin entry is missing its package/signature."],"exampleFix":"// before — registry builtin entry with no package\n{ \"id\": \"motrix.core\", \"version\": \"2.1.0\" }\n\n// after — include signed package descriptor\n{ \"id\": \"motrix.core\", \"version\": \"2.1.0\",\n  \"package\": { \"url\": \"https://dl.motrix.app/core/2.1.0.moext\", \"size\": 8192,\n    \"sha256\": \"<64 hex>\", \"signature\": \"<ed25519 base64>\" } }","handlingStrategy":"validation","validationCode":"function hasBuiltinPackage(entry: { package?: { url?: unknown; size?: unknown; sha256?: unknown; signature?: unknown } }): boolean {\n  const p = entry.package\n  return !!p && typeof p.url === 'string' && typeof p.size === 'number' && typeof p.sha256 === 'string' && typeof p.signature === 'string'\n}\nif (!hasBuiltinPackage(registryEntry)) {\n  throw new Error('builtin update entry has no signed package descriptor')\n}","typeGuard":"function isBuiltinUpdateEntry(e: unknown): e is { package: { url: string; size: number; sha256: string; signature: string } } {\n  const p = (e as { package?: unknown }).package\n  if (typeof p !== 'object' || p === null) return false\n  return typeof (p as { url?: unknown }).url === 'string' &&\n    typeof (p as { size?: unknown }).size === 'number' &&\n    typeof (p as { sha256?: unknown }).sha256 === 'string' &&\n    typeof (p as { signature?: unknown }).signature === 'string'\n}","tryCatchPattern":"try {\n  await updater.stage(entry, effective)\n} catch (e) {\n  if (e instanceof AppError && e.code === ErrorCode.PluginManifestInvalid && e.message === 'plugin.update.builtin_no_package') {\n    // registry listed a builtin version with no artifact; skip the update, keep current version\n    skipBuiltinUpdate(entry.id)\n  } else throw e\n}","preventionTips":["Publishing pipeline must reject builtin registry entries that lack package.url/size/sha256/signature.","Upload the signed package blob before listing the new builtin version in the registry index.","Gate BuiltinUpdater.stage on isBuiltinUpdateEntry(entry) so metadata-only entries are skipped."],"tags":["plugin","update","builtin","registry","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}