agalwood/Motrix · critical · AggregateError

plugin locale prepare-commit and rollback both failed

Error message

plugin locale prepare-commit and rollback both failed

What it means

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.

Source

Thrown at src/core/plugin/plugin-registry.ts:412

    options: HostLanguageTransactionOptions
  ): Promise<boolean> {
    const shouldCommit = options.shouldCommit ?? (() => true)
    for (;;) {
      if (!shouldCommit()) return false
      const prepared = await this.prepareHostLanguage(language)
      if (!this.canCommitHostLanguage(prepared, shouldCommit)) {
        if (!shouldCommit()) return false
        continue
      }

      const previousLanguage = this.currentLang
      try {
        await options.beforeCommit?.()
      } catch (error) {
        try {
          await options.rollbackHostLocale(previousLanguage)
        } catch (rollbackError) {
          throw new AggregateError(
            [error, rollbackError],
            'plugin locale prepare-commit and rollback both failed'
          )
        }
        throw error
      }

      if (!this.canCommitHostLanguage(prepared, shouldCommit)) {
        await options.rollbackHostLocale(previousLanguage)
        if (!shouldCommit()) return false
        continue
      }
      const committed = await this.commitHostLanguage(
        prepared,
        options.commitHostLocale,
        options.rollbackHostLocale,
        shouldCommit
      )

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Read AggregateError.errors[0] (beforeCommit) and AggregateError.errors[1] (rollbackHostLocale) to find the shared failing dependency.
  2. Repair the shared resource both callbacks rely on (release the file lock, restore IPC, fix permissions).
  3. Make rollbackHostLocale resilient — never re-throw the same class of failure; log and continue so the original error surfaces cleanly.
  4. Retry setHostLanguageTransaction once the dependency is healthy.

Example fix

// before — rollback re-throws on the same IPC failure
beforeCommit: () => hostIpc.persistLocale(lang),
rollbackHostLocale: () => hostIpc.persistLocale(prev)

// after — rollback degrades gracefully
rollbackHostLocale: (prev) => hostIpc.persistLocale(prev).catch(e => log.error('rollback failed', e))
Defensive patterns

Strategy: try-catch

Type guard

function isAggregateError(e: unknown): e is AggregateError {
  return e instanceof Error && Array.isArray((e as AggregateError).errors)
}

Try / catch

try {
  await registry.setHostLanguageTransaction(language, {
    beforeCommit: persistToHost,
    rollbackHostLocale: rollbackOnHost,
    commitHostLocale: commitOnHost,
    shouldCommit: () => true,
  })
} catch (e) {
  if (e instanceof AggregateError) {
    const [prepareErr, rollbackErr] = e.errors
    log.error('locale prepare-commit AND rollback failed', { prepareErr, rollbackErr })
    // registry in-memory state was not committed; safe to retry once the shared dependency recovers
  } else throw e
}

Prevention

When it happens

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

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

Related errors


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