actualbudget/actual · error · Error
getSyncError(result.error.reason, localBudget.id, result.err
Error message
getSyncError(result.error.reason, localBudget.id, result.error.meta)
What it means
Thrown by api/download-budget in packages/loot-core/src/server/api.ts after the local budget is loaded and handlers['sync-budget']() returns an error. The reason plus meta are formatted by getSyncError() into messages for out-of-sync-migrations/data, invalid-schema (newer DB schema), budget-not-found, or clock-drift. The original reason code is preserved on the thrown error.
Source
Thrown at packages/loot-core/src/server/api.ts:242
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,
);
}
return;
}
// Download the remote file (no need to perform a sync as the file will already be up-to-date)
const result = await handlers['download-budget']({
cloudFileId: remoteBudget.fileId,
});
if (result.error) {
logger.log('Full error details', result.error);
throw withErrorCode(
new Error(getDownloadError(result.error)),
result.error.reason,View on GitHub (pinned to d4334cb6e6)
Solutions
- Update Actual to the latest version on all devices (schema/migration mismatches)
- Sync system clock via NTP if reason is clock-drift
- If out-of-sync-data cannot resolve, back up and delete the stale local file, then re-download
- Read error.code for the precise reason and branch accordingly
Example fix
// before
await api.downloadBudget(syncId);
// after
try {
await api.downloadBudget(syncId);
} catch (e) {
if (e.code === 'clock-drift') { await syncClock(); await api.downloadBudget(syncId); }
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Clock sanity before syncing
const skewMin = Math.abs(Date.now() - Date.parse(await fetchServerTime())) / 60000;
if (skewMin > 5) console.warn('Clock drift detected; sync may fail with clock-drift'); Type guard
function isSyncError(e: unknown): e is { code: string } {
return typeof e === 'object' && e !== null && typeof (e as any).code === 'string';
} Try / catch
try {
await api.downloadBudget(syncId);
} catch (e) {
if (isSyncError(e) && e.code === 'clock-drift') { await syncNtp(); await api.downloadBudget(syncId); }
else if (isSyncError(e) && e.code === 'out-of-sync-data') await updateActualBeforeRetry();
else throw e;
} Prevention
- Run NTP time sync on hosts, containers, and CI runners
- Keep all Actual installations on the same version
- Back up and remove stale local budget files that cannot sync
- Check error.code before retrying — only clock-drift/network reasons are retry-safe
When it happens
Trigger: downloadBudget(syncId) with an existing local budget whose initial sync fails: local data out of sync with migrations, schema newer than app, device clock drifted from the sync server, or the local file's group id no longer exists on the server.
Common situations: Upgraded Actual partially (old app, new budget); Docker/VM clock skew; budget restored from backup into a mismatched app version; server wiped while a stale local file remains.
Related errors
- Timestamp.ClockDriftError
- invalid-schema
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- TrieNode for key ${k} could not be found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/a680c8dbf29979f2.
Report an issue: GitHub.