{"record":{"id":"4eaa3548d5de4b15","repo":"actualbudget/actual","slug":"no-id-returned-from-download","errorCode":null,"errorMessage":"No id returned from download.","messagePattern":"No id returned from download\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts","lineNumber":370,"sourceCode":"                error.meta &&\n                typeof error.meta === 'object' &&\n                'name' in error.meta &&\n                error.meta.name,\n            },\n          ),\n        );\n\n        return await dispatch(\n          downloadBudget({ cloudFileId, replace: true }),\n        ).unwrap();\n      } else {\n        dispatch(setAppState({ loadingText: null }));\n        alert(getDownloadError(error));\n      }\n      return null;\n    } else {\n      if (!id) {\n        throw new Error('No id returned from download.');\n      }\n      await Promise.all([\n        dispatch(loadGlobalPrefs()),\n        dispatch(loadAllFiles()),\n        dispatch(loadBudget({ id })),\n      ]);\n      dispatch(setAppState({ loadingText: null }));\n      return id;\n    }\n  },\n);\n\ntype LoadBackupPayload = {\n  budgetId: string;\n  backupId: string;\n};\n\n// Take in the budget id so that backups can be loaded when a budget isn't opened","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts#L352-L388","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst { id, error } = await send('download-budget', { cloudFileId });\nif (error) { ... }\n// after\nconst { id, error } = await send('download-budget', { cloudFileId });\nif (error) { ... }\nif (!id) {\n  throw new Error('Download succeeded but server returned no budget id. ' +\n    'Verify client and sync-server versions match.');\n}","handlingStrategy":"type-guard","validationCode":"// Pre-flight: confirm the cloud file exists and server is reachable\nconst { cloudFiles } = await send('get-budgets'); // or loadAllFiles state\nconst exists = cloudFiles.some(f => f.cloudFileId === cloudFileId);\nif (!exists) { alert('Cloud file not found: ' + cloudFileId); return; }","typeGuard":"type DownloadResult = { id?: string; error?: unknown };\nfunction hasDownloadId(\n  res: DownloadResult,\n): res is DownloadResult & { id: string } {\n  return typeof res.id === 'string' && res.id.length > 0;\n}","tryCatchPattern":"try {\n  const id = await dispatch(downloadBudget({ cloudFileId })).unwrap();\n  if (!id) { /* non-throwing error paths return null and alert internally */ return; }\n} catch (e) {\n  if (e instanceof Error && e.message === 'No id returned from download.') {\n    alert('Server returned a malformed response. Check that client and sync-server versions match.');\n  } else { throw e; }\n}","preventionTips":["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."],"tags":["sync-server","download","protocol-mismatch","invariant"],"backgroundTag":"unexpected-empty-response","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}