{"record":{"id":"eb53453a54cc4da0","repo":"actualbudget/actual","slug":"unauthorized","errorCode":"unauthorized","errorMessage":"unauthorized","messagePattern":"unauthorized","errorType":"error_code","errorClass":"FileUploadError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/cloud-storage.ts","lineNumber":292,"sourceCode":"      await fs.removeFile(dbFile);\n    }\n    if (await fs.exists(metaFile)) {\n      await fs.removeFile(metaFile);\n    }\n  } else {\n    await fs.mkdir(budgetDir);\n  }\n\n  await fs.writeFile(fs.join(budgetDir, 'db.sqlite'), dbContent);\n  await fs.writeFile(fs.join(budgetDir, 'metadata.json'), JSON.stringify(meta));\n\n  return { id: meta.id };\n}\n\nexport async function upload() {\n  const userToken = await asyncStorage.getItem('user-token');\n  if (!userToken) {\n    throw FileUploadError('unauthorized');\n  }\n\n  const exported = await exportBuffer();\n  if (exported == null) {\n    return;\n  }\n  const zipContent = exported.data;\n\n  const {\n    id,\n    groupId,\n    budgetName,\n    cloudFileId: originalCloudFileId,\n    encryptKeyId,\n  } = prefs.getPrefs();\n  let cloudFileId = originalCloudFileId;\n  let uploadContent = zipContent;\n  let uploadMeta = null;","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/cloud-storage.ts#L274-L310","documentation":"FileUploadError('unauthorized') is thrown at the start of upload() when there is no user-token stored in asyncStorage. Without a login token the library refuses to attempt the upload to the sync server. It is a client-side pre-check, distinct from a server-side 401.","triggerScenarios":"calling upload(), uploadBudget(), duplicateBudget(), createBudget(), loadBackup(), possiblyUpload()-triggered uploads, or importActual() while the user is not signed in to a sync server (or the token was never set / was cleared).","commonSituations":"running in local-only (no server) mode and trying to use cloud features, token invalidated by server restart with different key, expired login after long offline period, or headless/API usage where login() was never called.","solutions":["Sign in to the sync server (or call api.login/ bootstrap so user-token is stored) before uploading","Verify the app is configured with the correct server URL and that the server is running","If the token is stale, sign out and sign back in to obtain a fresh user-token","In headless/API scripts, call await api.login(password) (or init with config) before any upload"],"exampleFix":"// before\nawait upload();\n// after\nconst token = await asyncStorage.getItem('user-token');\nif (!token) { await signIn(password); }\nawait upload();","handlingStrategy":"validation","validationCode":"async function canUpload() {\n  const token = await asyncStorage.getItem('user-token');\n  if (!token) throw new Error('Not signed in: no user-token stored');\n  return token;\n}","typeGuard":"function hasUserToken(prefs) {\n  return typeof prefs?.['user-token'] === 'string' && prefs['user-token'].length > 0;\n}","tryCatchPattern":"try {\n  await upload();\n} catch (e) {\n  if (e instanceof FileUploadError && e.reason === 'unauthorized') {\n    await signIn(password); // obtain fresh user-token\n    await upload();\n  } else throw e;\n}","preventionTips":["Always login/bootstrap before cloud operations in scripts","Handle 'unauthorized' globally by re-authenticating once and retrying","Check the configured server URL matches a running sync server","Don't clear asyncStorage/user-token while a sync is pending"],"tags":["auth","unauthorized","cloud-sync","missing-token"],"backgroundTag":"missing-auth-token","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}