{"record":{"id":"a92f091d186b8bba","repo":"actualbudget/actual","slug":"internal-a92f09","errorCode":"internal","errorMessage":"internal","messagePattern":"internal","errorType":"error_code","errorClass":"FileUploadError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/cloud-storage.ts","lineNumber":363,"sourceCode":"          : null),\n        ...(groupId ? { 'X-ACTUAL-GROUP-ID': groupId } : null),\n        // TODO: fix me\n        // oxlint-disable-next-line typescript/no-explicit-any\n      },\n      body: uploadContent,\n    });\n  } catch (err) {\n    logger.log('Upload failure', err);\n\n    if (err instanceof PostError) {\n      throw FileUploadError(\n        err.reason === 'unauthorized'\n          ? 'unauthorized'\n          : err.reason || 'network',\n      );\n    }\n\n    throw FileUploadError('internal');\n  }\n\n  if (res.status === 'ok') {\n    // Only save it if we are still working on the same file\n    if (prefs.getPrefs() && prefs.getPrefs().id === id) {\n      await prefs.savePrefs({\n        lastUploaded: monthUtils.currentDay(),\n        cloudFileId,\n        groupId: res.groupId,\n      });\n    }\n  } else {\n    throw FileUploadError('internal');\n  }\n}\n\nexport async function possiblyUpload() {\n  const { cloudFileId, groupId, lastUploaded } = prefs.getPrefs();","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/cloud-storage.ts#L345-L381","documentation":"FileUploadError('internal') is thrown when the upload POST itself throws but the error is not a PostError — i.e., an unexpected exception (fetch transport crash, serialization bug, unhandled promise rejection) rather than a structured server response. It is a catch-all for non-classified upload exceptions.","triggerScenarios":"the fetchJSON call in upload() throws an exception that is not an instance of PostError — e.g. an abrupt network socket error, request construction failure, or an error thrown inside header/body preparation.","commonSituations":"proxy or TLS middleware throwing, undici/node fetch low-level failures, misconfigured server URL producing malformed requests, memory pressure with very large budgets.","solutions":["Check the app logs: 'Upload failure' is logged with the original error before this is thrown — fix the underlying cause","Retry the upload; transient transport errors often resolve","Verify the server URL configuration (serverURL) is a valid absolute URL","Update Actual — newer versions classify more failure modes properly"],"exampleFix":"// before: opaque internal failure\nawait upload();\n// after: capture underlying cause via logs and retry with backoff\nfor (let i = 0; i < 3; i++) {\n  try { await upload(); break; }\n  catch (e) { if (e.reason !== 'internal') throw e; await delay(1000 * 2 ** i); }\n}","handlingStrategy":"retry","validationCode":"function assertValidServerUrl(url) {\n  const u = new URL(url);\n  if (!/^https?:$/.test(u.protocol)) throw new Error(`invalid server URL: ${url}`);\n  return url;\n}","typeGuard":"function isInternalUploadError(e) {\n  return e instanceof FileUploadError && e.reason === 'internal';\n}","tryCatchPattern":"try {\n  await upload();\n} catch (e) {\n  if (isInternalUploadError(e)) {\n    logger.error('upload internal failure — check underlying cause in logs', e);\n    await backoffRetry(upload, 2); // may be transient transport issue\n  } else throw e;\n}","preventionTips":["Check the logged 'Upload failure' cause before assuming the worst","Keep Actual updated so more failure modes are classified properly","Validate server URL configuration at startup","Retry with exponential backoff for transient transport errors"],"tags":["network","cloud-sync","unclassified-error"],"backgroundTag":"unclassified-network-error","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}