immich-app/immich · error · BadRequestException

Invalid ack type: ${type}

Error message

Invalid ack type: ${type}

What it means

Thrown (as BadRequestException) in SyncService.setAcks when an ack decoded by fromAck(ack) yields a type that is not a member of the SyncEntityType enum. It is a placeholder guard (the TODO notes class-validator validation is planned) that rejects unknown ack types before they reach the checkpoint upsert.

Source

Thrown at server/src/services/sync.service.ts:113

    return this.syncCheckpointRepository.getAll(sessionId);
  }

  async setAcks(auth: AuthDto, dto: SyncAckSetDto) {
    const sessionId = auth.session?.id;
    if (!sessionId) {
      return throwSessionRequired();
    }

    const checkpoints: Record<string, Insertable<SessionSyncCheckpointTable>> = {};
    for (const ack of dto.acks) {
      const { type } = fromAck(ack);
      if (type === SyncEntityType.SyncResetV1) {
        await this.sessionRepository.resetSyncProgress(sessionId);
        return;
      }
      // TODO proper ack validation via class validator
      if (!Object.values(SyncEntityType).includes(type)) {
        throw new BadRequestException(`Invalid ack type: ${type}`);
      }

      // TODO pick the latest ack for each type, instead of using the last one
      checkpoints[type] = { sessionId, type, ack };
    }

    await this.syncCheckpointRepository.upsertAll(Object.values(checkpoints));
  }

  async deleteAcks(auth: AuthDto, dto: SyncAckDeleteDto) {
    const sessionId = auth.session?.id;
    if (!sessionId) {
      return throwSessionRequired();
    }

    await this.syncCheckpointRepository.deleteAll(sessionId, dto.types);
  }

View on GitHub (pinned to 199723261c)

Solutions

  1. Upgrade the client to match the server's SyncEntityType values; re-fetch the openapi schema.
  2. Drop unknown ack types on the client before submitting (filter against the documented enum).
  3. Pin client and server to the same Immich version until ack types stabilize.
  4. Inspect the request body; the failing type is echoed back in the message.

Example fix

// before
await api.syncAcks([{ type: 'AssetV1', ack: '...' }]);
// after
await api.syncAcks([{ type: 'AssetV2', ack: '...' }]);
Defensive patterns

Strategy: validation

Validate before calling

import { SyncEntityType } from './openapi';
const validTypes = new Set(Object.values(SyncEntityType));
const safeAcks = dto.acks.filter(a => validTypes.has(fromAck(a).type));
await syncApi.setAcks({ acks: safeAcks });

Type guard

function isKnownSyncEntityType(t: unknown): t is SyncEntityType {
  return typeof t === 'string' && Object.values(SyncEntityType).includes(t as SyncEntityType);
}

Try / catch

try { await syncApi.setAcks({ acks }); }
catch (e) {
  if (e instanceof BadRequestException && /Invalid ack type/.test(e.message)) {
    // re-fetch enum, filter unknown acks, retry once
  }
}

Prevention

When it happens

Trigger: POST /sync/acks with a body whose acks[].type string does not match any SyncEntityType value, e.g. an old client sending a renamed/removed entity type, or a hand-crafted payload.

Common situations: Client/server version skew (server upgraded, client still emits a v1 ack); typo in a custom integration; SDK regenerated against a newer enum than the deployed server.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/f8a3651b9c5fa476. Report an issue: GitHub.