{"record":{"id":"932761a1d7d4424c","repo":"actualbudget/actual","slug":"download-failure","errorCode":"download-failure","errorMessage":"download-failure","messagePattern":"download-failure","errorType":"error_code","errorClass":"FileDownloadError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/cloud-storage.ts","lineNumber":463,"sourceCode":"  const userToken = await asyncStorage.getItem('user-token');\n  const syncServer = getServer().SYNC_SERVER;\n\n  const userFileFetch = fetch(`${syncServer}/download-user-file`, {\n    headers: {\n      'X-ACTUAL-TOKEN': userToken,\n      'X-ACTUAL-FILE-ID': cloudFileId,\n    },\n  })\n    .then(checkHTTPStatus)\n    .then(res => {\n      if (res.arrayBuffer) {\n        return res.arrayBuffer().then(ab => Buffer.from(ab));\n      }\n      return res.buffer();\n    })\n    .catch(err => {\n      logger.log('Download failure', err);\n      throw FileDownloadError('download-failure');\n    });\n\n  const userFileInfoFetch = fetchJSON(`${syncServer}/get-user-file-info`, {\n    headers: {\n      'X-ACTUAL-TOKEN': userToken,\n      'X-ACTUAL-FILE-ID': cloudFileId,\n    },\n  }).catch(err => {\n    logger.log('Error fetching file info', err);\n    throw FileDownloadError('internal', { fileId: cloudFileId });\n  });\n\n  const [userFileInfoRes, userFileRes] = await Promise.all([\n    userFileInfoFetch,\n    userFileFetch,\n  ]);\n\n  if (userFileInfoRes.status !== 'ok') {","sourceCodeStart":445,"sourceCodeEnd":481,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/cloud-storage.ts#L445-L481","documentation":"FileDownloadError('download-failure') is thrown when the HTTP fetch of /download-user-file fails — either a non-OK HTTP status (checkHTTPStatus) or a network/transport error while reading the body into a Buffer. The budget archive was never retrieved.","triggerScenarios":"download() where the sync server returns 4xx/5xx for the file download (bad token, unknown fileId, server error) or the connection drops mid-download.","commonSituations":"wrong cloudFileId passed to api.downloadBudget, expired token, self-hosted server offline, proxy timeouts on large budgets, server deleting the file between listing and download.","solutions":["Verify the cloudFileId exists via listRemoteFiles before downloading","Sign in again to refresh the user-token, then retry","Check the sync server is running and reachable; retry on transient network errors","Check server logs for the /download-user-file request to see the actual HTTP error"],"exampleFix":"// before\nawait api.downloadBudget(cloudFileId);\n// after\nconst files = await listRemoteFiles();\nif (!files?.some(f => f.cloudFileId === cloudFileId)) {\n  throw new Error(`file ${cloudFileId} not on server`);\n}\nawait api.downloadBudget(cloudFileId);","handlingStrategy":"validation","validationCode":"async function assert downloadable(cloudFileId, token, syncUrl) {\n  const files = await listRemoteFiles();\n  if (!files?.some(f => f.cloudFileId === cloudFileId)) {\n    throw new Error(`cloudFileId ${cloudFileId} not present on server`);\n  }\n}","typeGuard":"function fileExistsRemotely(files, cloudFileId) {\n  return Array.isArray(files) && files.some(f => f?.cloudFileId === cloudFileId);\n}","tryCatchPattern":"try {\n  await download(cloudFileId);\n} catch (e) {\n  if (e instanceof FileDownloadError && e.reason === 'download-failure') {\n    await backoffRetry(() => download(cloudFileId), 3); // transient HTTP/network failure\n  } else throw e;\n}","preventionTips":["Validate the fileId against listRemoteFiles before downloading","Retry transient failures with backoff","Keep sessions fresh to avoid 401s on large slow downloads","Check server/proxy timeouts for big budget archives"],"tags":["network","http","download","cloud-sync"],"backgroundTag":"http-request-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}