{"record":{"id":"f7230edece3c4435","repo":"actualbudget/actual","slug":"file-not-found-f7230e","errorCode":"file-not-found","errorMessage":"file-not-found","messagePattern":"file-not-found","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-sync.ts","lineNumber":112,"sourceCode":"const verifyFileExists = (\n  fileId: unknown,\n  filesService: FilesService,\n  res: Response,\n  errorObject: string | Record<string, unknown>,\n) => {\n  if (typeof fileId !== 'string' || !isValidFileId(fileId)) {\n    res.status(400).send('invalid fileId');\n    return;\n  }\n\n  try {\n    return filesService.get(fileId);\n  } catch (e) {\n    if (e instanceof FileNotFound) {\n      //FIXME: error code should be 404. Need to make sure frontend is ok with it.\n      //TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message\n      // for every FileNotFound error\n      res.status(400).send(errorObject);\n      return;\n    }\n    throw e;\n  }\n};\n\nfunction requireFileOwner(file: File, userId: string) {\n  const isOwner = file.owner === userId;\n  const isServerAdmin = isAdmin(userId);\n  if (isOwner || isServerAdmin) {\n    return null;\n  }\n  return 'file-access-not-allowed';\n}\n\nfunction requireFileAccess(file: File, userId: string) {\n  if (requireFileOwner(file, userId) === null) {\n    return null;","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-sync.ts#L94-L130","documentation":"When the fileId is valid but filesService.get cannot find the file (throws FileNotFound), verifyFileExists responds 400 with the caller-supplied errorObject whose code is 'file-not-found'. Comments in the source note this should ideally be 404; it is kept 400 for frontend compatibility.","triggerScenarios":"A /sync/file, /sync/currentfile, or /sync call referencing a fileId that does not exist on this server — deleted budget, wrong server environment, or a file id from a different instance.","commonSituations":"Pointing a client at a fresh sync-server without importing/restoring the budget; switching between self-hosted and the cloud server; a budget deleted while another device still syncs it.","solutions":["Verify the server URL matches the environment that owns the budget, then re-upload/re-create the budget or restore from backup.","List files on the server and use an existing fileId.","If the budget is gone, create a new one and let the client upload it (full sync) instead of syncing the stale id."],"exampleFix":"// before\nawait fetch(`${serverUrl}/sync/file?fileId=${oldFileId}`); // 400 file-not-found\n// after: check existence first\nconst files = await (await fetch(`${serverUrl}/files`, { headers: authHeaders })).json();\nconst exists = files.data.some(f => f.fileId === oldFileId);\nif (!exists) await actual.createBudget({}); // or pick an existing fileId","handlingStrategy":"validation","validationCode":"const files = await (await fetch(serverUrl + '/files', { headers: authHeaders })).json();\nif (!files.data.some(f => f.fileId === fileId)) throw new Error('file ' + fileId + ' does not exist on this server');","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(`${serverUrl}/sync/file?fileId=${fileId}`, { headers: authHeaders });\n  if (res.status === 400) {\n    const err = await res.json();\n    if (err.code === 'file-not-found' || err.reason === 'file-not-found') throw new Error('budget missing on server: re-upload or restore it');\n  }\n  return res;\n} catch (e) { throw e; }","preventionTips":["Confirm the sync-server URL points at the environment that owns the budget","Restore/import budgets after migrating to a fresh server","Handle deleted budgets by re-creating and performing a full upload"],"tags":["http-400","file-not-found","sync","state-mismatch"],"backgroundTag":"resource-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}