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.
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)
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.
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
- 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.
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
- File not found
- Invalid file ID
- Invalid group ID
- Failed to delete user access: ${error.message}
- New owner not found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/69486a5cbc54f0ef.
Report an issue: GitHub.