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

  1. Inspect the row's group_id: SELECT id, group_id FROM files WHERE id = ? and compare to the expected format.
  2. Regenerate a valid group ID and update the row, or delete/re-create the file via the normal flow.
  3. Avoid manual writes to the files table; use FilesService.set() which assigns valid group IDs.
  4. 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

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


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