{"record":{"id":"4a35f9cf8368d151","repo":"actualbudget/actual","slug":"invalid-fileid","errorCode":null,"errorMessage":"invalid fileId","messagePattern":"invalid fileId","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-sync.ts","lineNumber":101,"sourceCode":"  const value = req.headers[key];\n  if (!value) {\n    return null;\n  }\n  if (typeof value !== 'string') {\n    res.status(400).send('Duplicate headers encountered for key ' + key);\n    return null;\n  }\n  return value;\n}\n\nconst 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) {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-sync.ts#L83-L119","documentation":"verifyFileExists validates the fileId supplied to file-scoped sync routes (e.g. /sync/file, /sync/currentfile). If it is not a string or fails isValidFileId, the route returns 400 with body 'invalid fileId'.","triggerScenarios":"Calling GET /sync/file or /sync/currentfile with a missing, empty, or malformed fileId (wrong format, URL-encoded incorrectly, numeric id).","commonSituations":"Stale ids saved in scripts after the budget was deleted and re-created; hand-built curl calls with a placeholder id; passing the group id instead of the file id.","solutions":["Pass a valid file id string, obtained from the files list endpoint for the authenticated user.","URL-encode the id properly when placing it in the query/path.","Re-download the list of budgets and use the current id if the file was recreated."],"exampleFix":"// before\ncurl \"$SERVER/sync/file?fileId=abc\"\n// after: use a real file id\nFILE_ID=$(curl -s -H \"$AUTH\" \"$SERVER/files\" | jq -r '.data[0].fileId')\ncurl -H \"$AUTH\" \"$SERVER/sync/file?fileId=$FILE_ID\"","handlingStrategy":"validation","validationCode":"const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nif (typeof fileId !== 'string' || !uuidRe.test(fileId)) throw new Error('pass a valid file id string to the sync file endpoints');","typeGuard":"const isFileId = (v) => typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);","tryCatchPattern":"try {\n  const res = await fetch(`${serverUrl}/sync/file?fileId=${fileId}`, { headers: authHeaders });\n  if (res.status === 400 && (await res.text()) === 'invalid fileId') throw new Error('fileId malformed; fetch a real id from /files');\n  return res;\n} catch (e) { throw e; }","preventionTips":["Source file ids from the /files listing, never literals","URL-encode ids placed in query strings","Distinguish file id vs group id in scripts"],"tags":["validation","http-400","sync","file-id"],"backgroundTag":"invalid-identifier","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}