actualbudget/actual · error · Error

getSyncError(error, id)

Error message

getSyncError(error, id)

What it means

Thrown by the api/load-budget handler in packages/loot-core/src/server/api.ts when loading a budget for the @actual-app/api fails with a sync error. The raw error reason is passed through getSyncError() (packages/loot-core/src/shared/errors.ts:141) which maps reasons like out-of-sync-migrations, invalid-schema, budget-not-found, and clock-drift to human-readable messages. The thrown error keeps the original reason code via withErrorCode.

Source

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

  }

  batchPromise.resolve();
  batchPromise = null;
};

handlers['api/load-budget'] = async function ({ id }) {
  const { id: currentId } = prefs.getPrefs() || {};

  if (currentId !== id) {
    connection.send('start-load');
    const { error } = await handlers['load-budget']({ id });

    if (!error) {
      connection.send('finish-load');
    } else {
      connection.send('show-budgets');

      throw withErrorCode(new Error(getSyncError(error, id)), error);
    }
  }
};

handlers['api/download-budget'] = async function ({ syncId, password }) {
  const { id: currentId } = prefs.getPrefs() || {};
  if (currentId) {
    await handlers['close-budget']();
  }

  const budgets = await handlers['get-budgets']();
  const localBudget = budgets.find(b => b.groupId === syncId);
  let remoteBudget: RemoteFile;

  // Load a remote file if we could not find the file locally
  if (!localBudget) {
    const files = await handlers['get-remote-files']();
    if (!files) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the budget id via getBudgetFiles()/list budgets on the Advanced settings page
  2. Update Actual to the latest version to match the budget's schema/migrations
  3. Fix the device clock (NTP sync) if the reason is clock-drift
  4. Check the thrown error's code property for the underlying reason and handle each case

Example fix

// before
await api.loadBudget('my-budget');
// after
try {
  await api.loadBudget(id);
} catch (e) {
  if (e.code === 'budget-not-found') id = await promptForValidId();
  else if (e.code === 'clock-drift') fixSystemClock();
  else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const files = await api.getBudgetFiles();
if (!files.some(f => f.id === budgetId)) throw new Error(`Budget ${budgetId} not available locally`);

Type guard

function hasErrorCode(e: unknown, code: string): e is { code: string } {
  return typeof e === 'object' && e !== null && (e as any).code === code;
}

Try / catch

try {
  await api.loadBudget(id);
} catch (e) {
  if (hasErrorCode(e, 'budget-not-found')) pickValidBudget();
  else if (hasErrorCode(e, 'clock-drift')) fixClockAndRetry();
  else if (hasErrorCode(e, 'invalid-schema')) await updateActual();
  else throw e;
}

Prevention

When it happens

Trigger: Calling loadBudget(id) from @actual-app/api where the budget needs a sync that fails: budget id not found locally/remotely, database schema newer than app version, migrations out of sync, or device clock drift versus sync server.

Common situations: Typo in the budget id passed to loadBudget; opening a budget created by a newer Actual version; system clock wrong (Docker containers, dual-boot machines); budget deleted on server.

Related errors


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