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
- Verify the budget id via getBudgetFiles()/list budgets on the Advanced settings page
- Update Actual to the latest version to match the budget's schema/migrations
- Fix the device clock (NTP sync) if the reason is clock-drift
- 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
- Validate budget ids against getBudgetFiles() before loading
- Keep Actual up to date across all devices
- Keep system clocks NTP-synced
- Log e.code to identify the underlying sync reason
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
- budget-not-found
- Error importing budget: ${result.error}
- Error importing budget: no budget was loaded
- Error exporting budget: ${result.error}
- Error exporting budget: no data was returned
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/01a02058df0b18ce.
Report an issue: GitHub.