{"record":{"id":"6e5108b574f85d05","repo":"agalwood/Motrix","slug":"plugin-locale-prepare-commit-and-rollback-both-fai","errorCode":null,"errorMessage":"plugin locale prepare-commit and rollback both failed","messagePattern":"plugin locale prepare-commit and rollback both failed","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"critical","filePath":"src/core/plugin/plugin-registry.ts","lineNumber":412,"sourceCode":"    options: HostLanguageTransactionOptions\n  ): Promise<boolean> {\n    const shouldCommit = options.shouldCommit ?? (() => true)\n    for (;;) {\n      if (!shouldCommit()) return false\n      const prepared = await this.prepareHostLanguage(language)\n      if (!this.canCommitHostLanguage(prepared, shouldCommit)) {\n        if (!shouldCommit()) return false\n        continue\n      }\n\n      const previousLanguage = this.currentLang\n      try {\n        await options.beforeCommit?.()\n      } catch (error) {\n        try {\n          await options.rollbackHostLocale(previousLanguage)\n        } catch (rollbackError) {\n          throw new AggregateError(\n            [error, rollbackError],\n            'plugin locale prepare-commit and rollback both failed'\n          )\n        }\n        throw error\n      }\n\n      if (!this.canCommitHostLanguage(prepared, shouldCommit)) {\n        await options.rollbackHostLocale(previousLanguage)\n        if (!shouldCommit()) return false\n        continue\n      }\n      const committed = await this.commitHostLanguage(\n        prepared,\n        options.commitHostLocale,\n        options.rollbackHostLocale,\n        shouldCommit\n      )","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/plugin-registry.ts#L394-L430","documentation":"Thrown by PluginRegistry.setHostLanguageTransaction: the prepare phase completed, options.beforeCommit() threw, then options.rollbackHostLocale(previousLanguage) also threw. Unlike error 205, the registry's own in-memory state had not yet been committed — only the caller's prepare hook and its rollback hook both failed, so the two errors are wrapped in an AggregateError.","triggerScenarios":"setHostLanguageTransaction() runs; options.beforeCommit rejects; the caller-supplied options.rollbackHostLocale also rejects. Common when beforeCommit and rollbackHostLocale both touch the same failing subsystem (host UI, IPC, disk).","commonSituations":"beforeCommit persists locale to a config file and rollbackHostLocale restores it, but the config file is locked/read-only. Host IPC is down so both the prepare-commit IPC and rollback IPC reject. A custom HostLanguageTransactionOptions implementation with a rollback that re-throws.","solutions":["Read AggregateError.errors[0] (beforeCommit) and AggregateError.errors[1] (rollbackHostLocale) to find the shared failing dependency.","Repair the shared resource both callbacks rely on (release the file lock, restore IPC, fix permissions).","Make rollbackHostLocale resilient — never re-throw the same class of failure; log and continue so the original error surfaces cleanly.","Retry setHostLanguageTransaction once the dependency is healthy."],"exampleFix":"// before — rollback re-throws on the same IPC failure\nbeforeCommit: () => hostIpc.persistLocale(lang),\nrollbackHostLocale: () => hostIpc.persistLocale(prev)\n\n// after — rollback degrades gracefully\nrollbackHostLocale: (prev) => hostIpc.persistLocale(prev).catch(e => log.error('rollback failed', e))","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isAggregateError(e: unknown): e is AggregateError {\n  return e instanceof Error && Array.isArray((e as AggregateError).errors)\n}","tryCatchPattern":"try {\n  await registry.setHostLanguageTransaction(language, {\n    beforeCommit: persistToHost,\n    rollbackHostLocale: rollbackOnHost,\n    commitHostLocale: commitOnHost,\n    shouldCommit: () => true,\n  })\n} catch (e) {\n  if (e instanceof AggregateError) {\n    const [prepareErr, rollbackErr] = e.errors\n    log.error('locale prepare-commit AND rollback failed', { prepareErr, rollbackErr })\n    // registry in-memory state was not committed; safe to retry once the shared dependency recovers\n  } else throw e\n}","preventionTips":["Ensure rollbackHostLocale cannot throw for the same reason beforeCommit does (different resource or try/catch inside).","Test the transaction with a beforeCommit that deliberately fails and assert rollback succeeds.","Keep the host-side locale IPC channel healthy so both phases can rely on it.","Log inside rollback so its failures are diagnosable without re-throwing."],"tags":["plugin","registry","locale","transactions","aggregate-error"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}