{"record":{"id":"69486a5cbc54f0ef","repo":"actualbudget/actual","slug":"could-not-update-file","errorCode":null,"errorMessage":"Could not update File","messagePattern":"Could not update File","errorType":"exception","errorClass":"GenericFileError","httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/app-sync/services/files-service.ts","lineNumber":256,"sourceCode":"      params.push(fileUpdate.encryptMeta);\n    }\n    if (fileUpdate.syncVersion !== undefined) {\n      updates.push('sync_version = ?');\n      params.push(fileUpdate.syncVersion);\n    }\n    if (fileUpdate.deleted !== undefined) {\n      updates.push('deleted = ?');\n      params.push(boolToInt(fileUpdate.deleted));\n    }\n\n    if (updates.length > 0) {\n      query += ' ' + updates.join(', ') + ' WHERE id = ?';\n      params.push(id);\n\n      const res = this.accountDb.mutate(query, params);\n\n      if (res.changes !== 1) {\n        throw new GenericFileError('Could not update File', { id });\n      }\n    }\n\n    // Return the modified object\n    const rawFile = this.getRaw(id);\n    if (!rawFile) {\n      throw new GenericFileError('File not found', { id });\n    }\n    return this.validate(rawFile);\n  }\n\n  getRaw(fileId: FileId): RawFile | null {\n    return this.accountDb.first(`SELECT * FROM files WHERE id = ?`, [fileId]);\n  }\n\n  validate(rawFile: RawFile) {\n    const fileId = rawFile.id;\n    if (!isValidFileId(fileId)) {","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-sync/services/files-service.ts#L238-L274","documentation":"GenericFileError thrown by FilesService.update() when the SQL UPDATE statement affects zero rows (res.changes !== 1). Since the caller already fetched the raw file before building the update, zero changes means the row vanished or the WHERE clause matched nothing.","triggerScenarios":"update(id, ...) called concurrently with a delete of the same file (row removed between read and write); passing an id whose row no longer exists; a race between two server instances writing the same file row.","commonSituations":"Two devices deleting/updating the same budget file simultaneously; admin purging users/files while a client sync is in flight; SQLite locking or restart wiping in-flight state in ephemeral deployments.","solutions":["Check that the file ID still exists before updating (SELECT via getRaw or get).","Retry the update after re-fetching the file if a concurrent delete removed it.","Ensure only one sync-server instance writes to the account SQLite DB.","Return 404-style handling in the route so clients can re-create or stop tracking the file."],"exampleFix":"// before\nawait filesService.update(fileId, { name });   // may throw 'Could not update File'\n// after\nif (filesService.getRaw(fileId)) {\n  await filesService.update(fileId, { name });\n}","handlingStrategy":"retry","validationCode":"// verify the row exists and retry-worthy before updating\nconst exists = filesService.getRaw(id);\nif (!exists) throw new Error(`Cannot update file ${id}: no longer exists`);","typeGuard":"function isUpdateFailure(err: unknown): err is Error {\n  return err instanceof Error && err.message === 'Could not update File';\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return filesService.update(id, patch);\n  } catch (err) {\n    if (isUpdateFailure(err) && filesService.getRaw(id)) continue; // transient race\n    throw err;\n  }\n}\nthrow new Error(`Failed to update file ${id} after retries`);","preventionTips":["Run a single sync-server writer against the account SQLite DB.","Check existence before update and handle concurrent deletes gracefully.","Wrap file mutations in transactions where possible.","Monitor for duplicate server instances in container deployments."],"tags":["database","update-failed","sync-server","race-condition"],"backgroundTag":"update-affected-zero-rows","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}