actualbudget/actual · error
No id returned from download.
Error message
No id returned from download.
What it means
`downloadBudget` calls `send('download-budget', { cloudFileId })`; when there is no `error`, the code assumes the server returned the new local budget `id`. If `id` is still falsy it throws 'No id returned from download.' — an invariant violation meaning the backend reported success but did not include the identifier needed to open the downloaded budget.
Source
Thrown at packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts:370
error.meta &&
typeof error.meta === 'object' &&
'name' in error.meta &&
error.meta.name,
},
),
);
return await dispatch(
downloadBudget({ cloudFileId, replace: true }),
).unwrap();
} else {
dispatch(setAppState({ loadingText: null }));
alert(getDownloadError(error));
}
return null;
} else {
if (!id) {
throw new Error('No id returned from download.');
}
await Promise.all([
dispatch(loadGlobalPrefs()),
dispatch(loadAllFiles()),
dispatch(loadBudget({ id })),
]);
dispatch(setAppState({ loadingText: null }));
return id;
}
},
);
type LoadBackupPayload = {
budgetId: string;
backupId: string;
};
// Take in the budget id so that backups can be loaded when a budget isn't openedView on GitHub (pinned to d4334cb6e6)
Solutions
- Update both client and sync-server to matching versions so the `download-budget` response shape includes `id`.
- Check the sync-server logs for the download request; confirm the handler returns `{ id }` on success.
- Bypass proxies/interceptors that may alter the response body and retry the download.
- Recreate the cloud file (re-upload) if its server-side record is corrupt, then download again.
Example fix
// before
const { id, error } = await send('download-budget', { cloudFileId });
if (error) { ... }
// after
const { id, error } = await send('download-budget', { cloudFileId });
if (error) { ... }
if (!id) {
throw new Error('Download succeeded but server returned no budget id. ' +
'Verify client and sync-server versions match.');
} Defensive patterns
Strategy: type-guard
Validate before calling
// Pre-flight: confirm the cloud file exists and server is reachable
const { cloudFiles } = await send('get-budgets'); // or loadAllFiles state
const exists = cloudFiles.some(f => f.cloudFileId === cloudFileId);
if (!exists) { alert('Cloud file not found: ' + cloudFileId); return; } Type guard
type DownloadResult = { id?: string; error?: unknown };
function hasDownloadId(
res: DownloadResult,
): res is DownloadResult & { id: string } {
return typeof res.id === 'string' && res.id.length > 0;
} Try / catch
try {
const id = await dispatch(downloadBudget({ cloudFileId })).unwrap();
if (!id) { /* non-throwing error paths return null and alert internally */ return; }
} catch (e) {
if (e instanceof Error && e.message === 'No id returned from download.') {
alert('Server returned a malformed response. Check that client and sync-server versions match.');
} else { throw e; }
} Prevention
- Keep desktop-client and sync-server on the same version to avoid response-shape drift.
- Do not proxy sync-server traffic through middleware that rewrites JSON bodies.
- When stubbing `download-budget` in tests/dev, always include a truthy `id` in success responses.
- Check server logs on any silent download anomaly before retrying blindly.
When it happens
Trigger: The `download-budget` IPC/server handler resolves without `error` but also without an `id` — typically a backend bug, a protocol/version mismatch between client and sync-server, or a custom/patched server that returns an empty success response for a cloud file download.
Common situations: Running a mismatched sync-server version against a newer client; proxy/middleware stripping the response payload; developing against a stubbed or partially implemented `download-budget` handler; response serialization dropping the id field.
Related errors
- getDownloadError(result.error)
- response.reason || response.error_code
- response.reason || response.error || fallbackMessage
- Failed to get server config.
- No sync server configured.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/4eaa3548d5de4b15.
Report an issue: GitHub.