{"record":{"id":"431fa04395687863","repo":"actualbudget/actual","slug":"error-exporting-budget-no-data-was-returned","errorCode":null,"errorMessage":"Error exporting budget: no data was returned","messagePattern":"Error exporting budget: no data was returned","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/methods.ts","lineNumber":94,"sourceCode":"\n  if (result.error) {\n    throw new Error(`Error importing budget: ${result.error}`);\n  }\n  if (!result.id) {\n    throw new Error('Error importing budget: no budget was loaded');\n  }\n  return { id: result.id };\n}\n\n/** Export the currently-loaded budget as a zip buffer. */\nexport async function exportBudget(): Promise<Uint8Array> {\n  const result = await send('export-budget');\n\n  if ('error' in result) {\n    throw new Error(`Error exporting budget: ${result.error}`);\n  }\n  if (!result.data) {\n    throw new Error('Error exporting budget: no data was returned');\n  }\n  return new Uint8Array(result.data);\n}\n\nfunction toArrayBuffer(data: ArrayBuffer | Uint8Array): ArrayBuffer {\n  if (data instanceof Uint8Array) {\n    // Copy into a fresh ArrayBuffer so that views into a larger (possibly\n    // shared) buffer are not sent across the worker boundary as-is.\n    const copy = new Uint8Array(data.byteLength);\n    copy.set(data);\n    return copy.buffer;\n  }\n  return data;\n}\n\nexport async function sync() {\n  return send('api/sync');\n}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/api/methods.ts#L76-L112","documentation":"If the export command neither errors nor includes data, the API throws rather than returning an empty buffer. This is a consistency guard: an export response without a payload is treated as a failure.","triggerScenarios":"The core 'export-budget' handler resolves with an unexpected/empty response — usually from a version mismatch between @actual-app/api and the running core/server, or an export handler path that returns nothing.","commonSituations":"Mixing versions of @actual-app/api and sync-server in a deployment; calling exportBudget before any budget is actually loaded; a stale long-running process after an upgrade.","solutions":["Upgrade @actual-app/api and the server to matching versions.","Verify a budget is loaded (e.g. call a budget query first) before exporting.","Restart the API process after upgrades to clear stale state.","Report upstream with versions if it reproduces on a loaded budget."],"exampleFix":"// before\nconst zip = await api.exportBudget();\n// after\nawait api.downloadBudget(budgetId); // ensure budget loaded\nconst zip = await api.exportBudget();\nif (!zip || zip.byteLength === 0) throw new Error('Export produced empty archive');","handlingStrategy":"validation","validationCode":"const zip = await api.exportBudget();\nif (!(zip instanceof Uint8Array) || zip.byteLength === 0) {\n  throw new Error('Export returned no usable data');\n}","typeGuard":"function isNonEmptyBytes(v: unknown): v is Uint8Array {\n  return v instanceof Uint8Array && v.byteLength > 0;\n}","tryCatchPattern":"let zip: Uint8Array;\ntry {\n  const out = await api.exportBudget();\n  if (!isNonEmptyBytes(out)) throw new Error('no data was returned');\n  zip = out;\n} catch (e) {\n  console.error('Export unavailable:', e.message);\n  await api.downloadBudget(id); // reload then retry\n  zip = await api.exportBudget();\n}","preventionTips":["Verify budget is loaded before export (successful query first).","Check exported zip byte length and PK header before persisting.","Restart processes after version upgrades.","Pin matching versions of @actual-app/api and sync-server."],"tags":["export","budget","api","version-mismatch"],"backgroundTag":"budget-export-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}