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
- 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.
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
- 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.
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
- PLUGIN_MANIFEST_INVALID
- invalid Chrome extension ID: ${id}
- invalid Firefox extension ID: ${id}
- PluginRuntimeFault
- PluginManifestInvalid
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/7b2a3a38a6d903e4.
Report an issue: GitHub.