{"record":{"id":"db1695c9b99aaade","repo":"agalwood/Motrix","slug":"pluginmanifestinvalid-db1695","errorCode":"PluginManifestInvalid","errorMessage":"plugin.update.builtin_staging_not_found","messagePattern":"plugin\\.update\\.builtin_staging_not_found","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/update/builtin-updater.ts","lineNumber":142,"sourceCode":"        pluginId: entry.id,\n        stagingDir,\n      })\n      return {\n        stagingId,\n        trustChanged: diff.changed,\n        added: diff.added,\n        newVersion: parsed.version,\n      }\n    } catch (e) {\n      await rm(stagingDir, { recursive: true, force: true })\n      throw e\n    }\n  }\n\n  async commit(stagingId: string): Promise<{ pluginId: string }> {\n    const staged = this.pending.get(stagingId)\n    if (!staged) {\n      throw new AppError(\n        ErrorCode.PluginManifestInvalid,\n        'plugin.update.builtin_staging_not_found'\n      )\n    }\n    const finalDir = path.join(this.opts.overlayDir, staged.pluginId)\n    const now = this.opts.now ?? Date.now\n    const backup = path.join(\n      this.opts.overlayDir,\n      `.bak-${staged.pluginId}-${now()}`\n    )\n    let hadPrevious = true\n    try {\n      await rename(finalDir, backup)\n    } catch {\n      hadPrevious = false\n    }\n    try {\n      await rename(staged.stagingDir, finalDir)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/update/builtin-updater.ts#L124-L160","documentation":"Thrown by BuiltinUpdater.commit(stagingId) when the given stagingId is not present in the in-memory pending Map. pending is populated only by stage() and cleared by commit()/cancel(); it is not persisted. Used to refuse committing an unknown, already-committed, already-cancelled, or post-restart staging id.","triggerScenarios":"Calling commit(stagingId) where: stagingId was never returned by stage(); commit() or cancel() already removed it; or the process restarted (the pending Map is per-instance memory).","commonSituations":"Two-phase UI that calls stage() in one process and commit() in another (e.g. renderer requests commit after an Electron main restart); retry logic calling commit() twice for the same staging id; calling commit() after the user already hit cancel(); a stale stagingId read from a log/old state.","solutions":["Treat commit() as ephemeral: re-stage() after any process restart before committing.","Track committed/cancelled stagingIds client-side and avoid double-commit.","If the staging dir still exists on disk, do not call commit() — re-stage from scratch since pending state is gone."],"exampleFix":"// before\n//   const { stagingId } = await updater.stage(entry, effective)\n//   // ... process restarts, pending Map is empty ...\n//   await updater.commit(stagingId)  // throws builtin_staging_not_found\n// after\n//   // pending does not survive restart; re-stage if needed, or commit before restart\n//   const result = await updater.stage(entry, effective)\n//   await updater.commit(result.stagingId)  // same process lifetime","handlingStrategy":"validation","validationCode":"function isStaged(updater: BuiltinUpdater, stagingId: string): boolean {\n  // pending is private; track stagingIds you received from stage() in your own set\n  return receivedStagingIds.has(stagingId) && !consumedStagingIds.has(stagingId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await updater.commit(stagingId);\n} catch (e) {\n  if (e instanceof AppError && e.message === 'plugin.update.builtin_staging_not_found') {\n    // staging was already committed/cancelled or process restarted; re-stage if update is still wanted\n  } else throw e;\n}","preventionTips":["Keep stage() and commit() within the same process lifetime — the pending Map is not persisted.","Track stagingIds in your own state and mark them consumed after commit/cancel to avoid double-commit.","If the app may restart between stage and commit, redesign to stage-and-commit in one step."],"tags":["plugin","builtin-updater","lifecycle","state","staging"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}