actualbudget/actual · error · Error

getTestKeyError(result.error)

Error message

getTestKeyError(result.error)

What it means

Thrown by api/download-budget in packages/loot-core/src/server/api.ts after handlers['key-test'] fails to validate the supplied password against the budget's encryption key. The reason is mapped to a message by getTestKeyError() (packages/loot-core/src/shared/errors.ts:128) — network, old-key-style, or decrypt-failure — and the original reason becomes the thrown error's code.

Source

Thrown at packages/loot-core/src/server/api.ts:230

  const activeFile = remoteBudget ? remoteBudget : localBudget;

  // Set the e2e encryption keys
  if (activeFile.encryptKeyId) {
    if (!password) {
      throw withErrorCode(
        new Error(
          `File ${activeFile.name} is encrypted. Please provide a password.`,
        ),
        'missing-key',
      );
    }

    const result = await handlers['key-test']({
      cloudFileId: remoteBudget ? remoteBudget.fileId : localBudget.cloudFileId,
      password,
    });
    if (result.error) {
      throw withErrorCode(
        new Error(getTestKeyError(result.error)),
        result.error.reason,
      );
    }
  }

  // Sync the local budget file
  if (localBudget) {
    await handlers['load-budget']({ id: localBudget.id });
    const result = await handlers['sync-budget']();
    if (result.error) {
      throw withErrorCode(
        new Error(
          getSyncError(result.error.reason, localBudget.id, result.error.meta),
        ),
        result.error.reason,
      );
    }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-check the password — the 'decrypt-failure' reason means it did not match
  2. For 'old-key-style', recreate the key on a device holding the file or use an older Actual version
  3. For 'network', verify server connectivity and retry
  4. Inspect the thrown error's code property to distinguish the three reasons

Example fix

// before
await api.downloadBudget(syncId, { password: pw });
// after
try {
  await api.downloadBudget(syncId, { password: pw });
} catch (e) {
  if (e.code === 'decrypt-failure') throw new Error('Wrong budget encryption password');
  throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify the password with key-test before the full download
const res = await keyTest(cloudFileId, password);
if (res.error) throw new Error(`Key check failed: ${res.error.reason}`);

Type guard

function isKeyError(e: unknown): e is { reason: 'network' | 'old-key-style' | 'decrypt-failure' } {
  return ['network', 'old-key-style', 'decrypt-failure'].includes((e as any)?.reason);
}

Try / catch

try {
  await api.downloadBudget(syncId, { password });
} catch (e) {
  if (e.code === 'decrypt-failure') throw new Error('Wrong password');
  if (e.code === 'old-key-style') throw new Error('Recreate key on a device with the file');
  throw e;
}

Prevention

When it happens

Trigger: downloadBudget(syncId, { password }) where key-test returns an error: wrong password (decrypt-failure), file uses an old unsupported key style (old-key-style), or the server cannot be reached to fetch key info (network).

Common situations: Password rotated on another device so the stored one is stale; typo'd password in env vars; budgets encrypted with pre-24.x key style being downloaded by a newer version; flaky network in CI.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/0481c042e4b373ab. Report an issue: GitHub.