agalwood/Motrix · error · AppError

PLUGIN_MANIFEST_INVALID

PLUGIN_MANIFEST_INVALID

Error message

plugin.update.builtin_no_package

What it means

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.

Source

Thrown at src/core/plugin/update/builtin-updater.ts:51

interface StagedUpdate {
  pluginId: string
  stagingDir: string
}

const OVERLAY_META = '_overlay.json'

export class BuiltinUpdater {
  private readonly pending = new Map<string, StagedUpdate>()

  constructor(private readonly opts: BuiltinUpdaterOptions) {}

  async stage(
    entry: RegistryPluginDTO,
    effective: PluginManifest
  ): Promise<BuiltinStageResult> {
    const pkg = entry.package
    if (!pkg) {
      throw new AppError(
        ErrorCode.PluginManifestInvalid,
        'plugin.update.builtin_no_package'
      )
    }
    if (!pkg.signature) {
      throw new AppError(
        ErrorCode.PluginManifestInvalid,
        'plugin.update.builtin_no_signature'
      )
    }
    if (!semverGt(entry.version, effective.version)) {
      throw new AppError(
        ErrorCode.PluginManifestInvalid,
        'plugin.update.builtin_not_newer'
      )
    }

    const bytes = await fetchVerifiedPackageBytes(entry, this.opts.fetchImpl)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Publish the package artifact and signature for the builtin version, then regenerate the registry entry so it includes package { url, size, sha256, signature }.
  2. Skip the update on the client until the registry entry is complete (the running builtin stays at its current version).
  3. Audit the registry publishing pipeline to fail when a builtin entry is missing its package/signature.

Example fix

// before — registry builtin entry with no package
{ "id": "motrix.core", "version": "2.1.0" }

// after — include signed package descriptor
{ "id": "motrix.core", "version": "2.1.0",
  "package": { "url": "https://dl.motrix.app/core/2.1.0.moext", "size": 8192,
    "sha256": "<64 hex>", "signature": "<ed25519 base64>" } }
Defensive patterns

Strategy: validation

Validate before calling

function hasBuiltinPackage(entry: { package?: { url?: unknown; size?: unknown; sha256?: unknown; signature?: unknown } }): boolean {
  const p = entry.package
  return !!p && typeof p.url === 'string' && typeof p.size === 'number' && typeof p.sha256 === 'string' && typeof p.signature === 'string'
}
if (!hasBuiltinPackage(registryEntry)) {
  throw new Error('builtin update entry has no signed package descriptor')
}

Type guard

function isBuiltinUpdateEntry(e: unknown): e is { package: { url: string; size: number; sha256: string; signature: string } } {
  const p = (e as { package?: unknown }).package
  if (typeof p !== 'object' || p === null) return false
  return typeof (p as { url?: unknown }).url === 'string' &&
    typeof (p as { size?: unknown }).size === 'number' &&
    typeof (p as { sha256?: unknown }).sha256 === 'string' &&
    typeof (p as { signature?: unknown }).signature === 'string'
}

Try / catch

try {
  await updater.stage(entry, effective)
} catch (e) {
  if (e instanceof AppError && e.code === ErrorCode.PluginManifestInvalid && e.message === 'plugin.update.builtin_no_package') {
    // registry listed a builtin version with no artifact; skip the update, keep current version
    skipBuiltinUpdate(entry.id)
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/7b2a3a38a6d903e4. Report an issue: GitHub.