actualbudget/actual · error · GenericFileError

File not found

Error message

File not found

What it means

GenericFileError thrown at the end of FilesService.update(): after the UPDATE ran (possibly affecting rows loosely), the code re-fetches the row with getRaw(id) and throws if it is null. This guards the return path — the file must exist and be readable after an update.

Source

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

      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)) {
      throw new GenericFileError('Invalid file ID', { fileId });
    }

    let groupId: GroupId | null = null;
    if (rawFile.group_id !== null) {
      if (!isValidGroupId(rawFile.group_id)) {
        throw new GenericFileError('Invalid group ID', {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the file ID exists before calling update().
  2. Handle the concurrent-delete case: re-list files and stop updating deleted ones.
  3. Serialize file mutations (single writer / transactions) to avoid read-after-write races.
  4. Catch GenericFileError in the route and respond 404 instead of 500.

Example fix

// before
const f = filesService.update(id, patch);   // throws 'File not found'
// after
const f = filesService.getRaw(id) && filesService.update(id, patch);
Defensive patterns

Strategy: try-catch

Validate before calling

const raw = filesService.getRaw(id);
if (!raw) throw new Error(`File ${id} does not exist; skipping update`);

Type guard

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

Try / catch

try {
  return filesService.update(id, patch);
} catch (err) {
  if (isFileMissingAfterUpdate(err)) {
    // row vanished mid-update: re-list files and surface 404 to the client
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: The row was deleted after the UPDATE statement executed; getRaw returns null because the id never existed and the update's WHERE clause silently matched nothing before this check; database read inconsistency mid-transaction.

Common situations: Concurrent DELETE racing an in-flight update; calling update with a bogus id against a build where changes is not checked; hard-resetting the account DB while requests are open.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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