actualbudget/actual · error · Error
getDownloadError(result.error)
Error message
getDownloadError(result.error)
What it means
Thrown by api/download-budget in packages/loot-core/src/server/api.ts when handlers['download-budget'] fails to fetch and import the remote file. getDownloadError() (packages/loot-core/src/shared/errors.ts:76) maps reasons — network/download-failure, invalid zip/meta, zip-too-large, decrypt-failure, out-of-sync-migrations, clock-drift — to actionable messages, and the reason becomes the error code. Full details are logged via logger before throwing.
Source
Thrown at packages/loot-core/src/server/api.ts:258
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,
);
}
await handlers['load-budget']({ id: result.id });
};
handlers['api/get-budgets'] = async function () {
const budgets = await handlers['get-budgets']();
const files = (await handlers['get-remote-files']()) || [];
return [
...budgets.map(file => budgetModel.toExternal(file)),
...files.map(file => remoteFileModel.toExternal(file)).filter(file => file),
];
};
handlers['api/sync'] = async function () {
const { id } = prefs.getPrefs();View on GitHub (pinned to d4334cb6e6)
Solutions
- Check the logged 'Full error details' and the error.code for the precise reason
- For network/download-failure, verify server health and retry
- For decrypt-failure, supply the correct password to downloadBudget
- For zip-too-large/invalid-zip, inspect the server's stored file and restore a clean copy from a working client
Example fix
// before
await api.downloadBudget(syncId);
// after
try {
await api.downloadBudget(syncId, { password });
} catch (e) {
if (['network', 'download-failure'].includes(e.code)) await retry(() => api.downloadBudget(syncId, { password }), 3);
else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
const res = await fetch(`${serverUrl}/health`);
if (!res.ok) throw new Error('Server unhealthy before download'); Type guard
function isDownloadReason(r: string): boolean {
return ['network','download-failure','not-zip-file','invalid-zip-file','invalid-meta-file','zip-too-large','decrypt-failure','out-of-sync-migrations','clock-drift'].includes(r);
} Try / catch
try {
await api.downloadBudget(syncId, { password });
} catch (e) {
if (['network', 'download-failure'].includes(e.code)) {
await backoffRetry(() => api.downloadBudget(syncId, { password }), 3);
} else if (e.code === 'decrypt-failure') {
throw new Error('Bad password: ' + getDownloadError(e));
} else throw e;
} Prevention
- Log and inspect error.code — getDownloadError maps each reason to a specific fix
- Retry network/download-failure with exponential backoff
- Monitor server disk integrity; truncated zips cause invalid-zip-file errors
- Keep clocks synced and app updated to avoid clock-drift and migration failures
When it happens
Trigger: downloadBudget(syncId) reaching the actual cloud download step and failing: server unreachable mid-transfer, corrupted/partial zip stored server-side, archive exceeding size limits, wrong password for E2E-encrypted file, or clock drift detected during transfer.
Common situations: Self-hosted server data directory with truncated .zip files; budget exceeding max file size; unstable network in CI pipelines; encryption password changed between key-test and download; container clock skew.
Related errors
- No id returned from download.
- zipMeta ? getUnsafeZipError(zipMeta) : error
- response.reason || response.error_code
- response.reason || response.error || fallbackMessage
- Failed to get server config.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/965ff1ec8c825f37.
Report an issue: GitHub.