{"record":{"id":"6a473adc34eda918","repo":"agalwood/Motrix","slug":"plugin-locale-commit-and-rollback-both-failed","errorCode":null,"errorMessage":"plugin locale commit and rollback both failed","messagePattern":"plugin locale commit and rollback both failed","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"critical","filePath":"src/core/plugin/plugin-registry.ts","lineNumber":373,"sourceCode":"    try {\n      onCommit()\n    } catch (error) {\n      this.currentLang = previousLanguage\n      for (const {\n        pluginId,\n        indexed,\n        manifest,\n        dictionaries,\n      } of previousEntries) {\n        indexed.manifest = manifest\n        if (dictionaries) this.localeDictionaries.set(pluginId, dictionaries)\n        else this.localeDictionaries.delete(pluginId)\n      }\n      this.registryRevision += 1\n      try {\n        await onRollback(previousLanguage)\n      } catch (rollbackError) {\n        throw new AggregateError(\n          [error, rollbackError],\n          'plugin locale commit and rollback both failed'\n        )\n      }\n      throw error\n    }\n    return true\n  }\n\n  /** Atomically switch registry-only consumers such as manifest list tests. */\n  async setHostLanguage(language: SupportedLocale): Promise<void> {\n    await this.setHostLanguageTransaction(language, {\n      commitHostLocale: () => {},\n      rollbackHostLocale: () => {},\n    })\n  }\n\n  /** Prepare asynchronously, then atomically commit registry + host locale. */","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/plugin-registry.ts#L355-L391","documentation":"Thrown by PluginRegistry.commitHostLanguage: the in-memory locale switch was already applied (currentLang and per-plugin manifests/dictionaries updated, registryRevision bumped), the caller's onCommit() threw, the registry then attempted onRollback(previousLanguage), and the rollback itself also threw. Because both halves failed, the registry wraps the original error and the rollback error in an AggregateError so neither fault is hidden.","triggerScenarios":"commitHostLanguage() is called; onCommit() rejects/throws; the onRollback callback supplied by the caller also rejects. Reproduces whenever a host locale switch's commit hook and rollback hook both depend on the same broken resource (e.g. both write to an unwritable disk).","commonSituations":"Disk full or userDataDir read-only so both commit persistence and rollback persistence fail. A host-side IPC channel died, so both the commit and rollback IPC calls reject. Concurrent locale switches racing the same callbacks.","solutions":["Inspect AggregateError.errors[0] (original commit error) and AggregateError.errors[1] (rollback error) to identify the shared root cause.","Fix the underlying resource both callbacks depend on (free disk, restore the IPC channel, fix permissions on userDataDir).","Make onRollback defensive so it cannot throw for the same reason onCommit did (e.g. rollback writes to a fallback location).","After recovery, reconcile registry state: call setHostLanguage with the desired locale again, since the registry may be left at an inconsistent revision."],"exampleFix":"// before — onCommit and onRollback both throw on disk write\ncommitHostLocale: () => fs.writeFileSync(indexPath, buf),\nrollbackHostLocale: () => fs.writeFileSync(indexPath, prevBuf)\n\n// after — rollback tolerates write failure and logs instead of throwing\nrollbackHostLocale: () => { try { fs.writeFileSync(indexPath, prevBuf) } catch (e) { log.error(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.commitHostLanguage(prepared, onCommit, onRollback)\n} catch (e) {\n  if (e instanceof AggregateError) {\n    const [commitErr, rollbackErr] = e.errors\n    log.error('locale commit AND rollback failed', { commitErr, rollbackErr })\n    // registry may be inconsistent at registryRevision; reconcile by re-applying the target locale\n    await registry.setHostLanguage(targetLanguage).catch(() => null)\n  } else throw e\n}","preventionTips":["Make onRollback resilient: never re-throw the same class of failure as onCommit; log and continue.","Keep commit and rollback dependencies separated so one failing resource cannot break both.","Persist locale state idempotently so a re-apply after a double fault converges.","Back up the registry index before locale switches so you can reconcile manually."],"tags":["plugin","registry","locale","transactions","aggregate-error"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}