actualbudget/actual · error · GenericFileError
Invalid group ID
Error message
Invalid group ID
What it means
GenericFileError thrown by FilesService.validate() when the file's group_id is non-null but fails isValidGroupId(). The group ID ties the file to its CRDT sync group, so a malformed group ID makes the file unusable for sync even though its own ID may be valid.
Source
Thrown at packages/sync-server/src/app-sync/services/files-service.ts:281
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', {
groupId: rawFile.group_id,
});
}
groupId = rawFile.group_id;
}
return new File({
id: fileId,
name: rawFile.name,
groupId,
encryptSalt: rawFile.encrypt_salt,
encryptTest: rawFile.encrypt_test,
encryptKeyId: rawFile.encrypt_keyid,
encryptMeta: rawFile.encrypt_meta,
syncVersion: rawFile.sync_version,
deleted: Boolean(rawFile.deleted),
owner: rawFile.owner,
});View on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the row's group_id: SELECT id, group_id FROM files WHERE id = ? and compare to the expected format.
- Regenerate a valid group ID and update the row, or delete/re-create the file via the normal flow.
- Avoid manual writes to the files table; use FilesService.set() which assigns valid group IDs.
- If migrating from an old server, follow the documented upgrade path rather than copying rows.
Example fix
// before UPDATE files SET group_id = 'grp' WHERE id = ?; // invalid -> 'Invalid group ID' // after UPDATE files SET group_id = ? WHERE id = ?; // pass a valid GroupId (uuid)
Defensive patterns
Strategy: validation
Validate before calling
import { isValidGroupId } from './utils';
const raw = filesService.getRaw(id);
if (raw && raw.group_id !== null && !isValidGroupId(raw.group_id)) {
throw new Error(`File ${id} has a corrupt group_id: ${raw.group_id}`);
} Type guard
function hasValidGroupId(raw: { group_id: string | null }): boolean {
return raw.group_id === null || isValidGroupId(raw.group_id);
} Try / catch
try {
return filesService.find();
} catch (err) {
if (err instanceof Error && err.message === 'Invalid group ID') {
logger.warn('Corrupt group_id in files table; re-creating group');
return repairGroupIds();
}
throw err;
} Prevention
- Assign group IDs only through FilesService.set().
- Keep the server and client on compatible versions when migrating.
- Validate group_id format in any import/restore scripts.
- Back up the account DB before schema migrations.
When it happens
Trigger: Rows where group_id was written in a wrong format (not the expected GroupId shape) — e.g. manual inserts, partial migrations, or tests fabricating group IDs; validate() runs inside get/find/update so any read or update of such a row throws.
Common situations: Dev databases seeded by hand; restoring partial backups where files rows survived but group data did not; older Actual versions with different ID formats being read by a newer server.
Related errors
- Invalid file ID
- Category '${category.name}' already exists in group '${categ
- An '${existingGroup.name}' account group already exists.
- ${name} is missing field ${String(field)}
- Could not update File
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/b9a92fb2a581da94.
Report an issue: GitHub.