actualbudget/actual · error · GenericFileError

Could not update File

Error message

Could not update File

What it means

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.

Solutions

  1. Check that the file ID still exists before updating (SELECT via getRaw or get).
  2. Retry the update after re-fetching the file if a concurrent delete removed it.
  3. Ensure only one sync-server instance writes to the account SQLite DB.
  4. Return 404-style handling in the route so clients can re-create or stop tracking the file.

Example fix

// before
await filesService.update(fileId, { name });   // may throw 'Could not update File'
// after
if (filesService.getRaw(fileId)) {
  await filesService.update(fileId, { name });
}
Defensive patterns

Strategy: retry

Validate before calling

// verify the row exists and retry-worthy before updating
const exists = filesService.getRaw(id);
if (!exists) throw new Error(`Cannot update file ${id}: no longer exists`);

Type guard

function isUpdateFailure(err: unknown): err is Error {
  return err instanceof Error && err.message === 'Could not update File';
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return filesService.update(id, patch);
  } catch (err) {
    if (isUpdateFailure(err) && filesService.getRaw(id)) continue; // transient race
    throw err;
  }
}
throw new Error(`Failed to update file ${id} after retries`);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/69486a5cbc54f0ef. Report an issue: GitHub.

Appendix: source

Thrown at packages/sync-server/src/app-sync/services/files-service.ts:256

      params.push(fileUpdate.encryptMeta);
    }
    if (fileUpdate.syncVersion !== undefined) {
      updates.push('sync_version = ?');
      params.push(fileUpdate.syncVersion);
    }
    if (fileUpdate.deleted !== undefined) {
      updates.push('deleted = ?');
      params.push(boolToInt(fileUpdate.deleted));
    }

    if (updates.length > 0) {
      query += ' ' + updates.join(', ') + ' WHERE id = ?';
      params.push(id);

      const res = this.accountDb.mutate(query, params);

      if (res.changes !== 1) {
        throw new GenericFileError('Could not update File', { id });
      }
    }

    // Return the modified object
    const rawFile = this.getRaw(id);
    if (!rawFile) {
      throw new GenericFileError('File not found', { id });
    }
    return this.validate(rawFile);
  }

  getRaw(fileId: FileId): RawFile | null {
    return this.accountDb.first(`SELECT * FROM files WHERE id = ?`, [fileId]);
  }

  validate(rawFile: RawFile) {
    const fileId = rawFile.id;
    if (!isValidFileId(fileId)) {

View on GitHub (pinned to d4334cb6e6)