immich-app/immich · warning · BadRequestException

No fields to update

Error message

No fields to update

What it means

SessionService.update() rejects empty PATCH bodies: it counts dto properties whose value is not undefined and, if zero remain, throws 'No fields to update' (session.service.ts:58, BadRequestException / HTTP 400). This guards the repository write from no-op updates.

Source

Thrown at server/src/services/session.service.ts:58

      expiresAt: dto.duration ? DateTime.now().plus({ seconds: dto.duration }).toJSDate() : null,
      deviceType: dto.deviceType,
      deviceOS: dto.deviceOS,
      token: hashed,
    });

    return { ...mapSession(session), token };
  }

  async getAll(auth: AuthDto): Promise<SessionResponseDto[]> {
    const sessions = await this.sessionRepository.getByUserId(auth.user.id);
    return sessions.map((session) => mapSession(session, auth.session?.id));
  }

  async update(auth: AuthDto, id: string, dto: SessionUpdateDto): Promise<SessionResponseDto> {
    await this.requireAccess({ auth, permission: Permission.SessionUpdate, ids: [id] });

    if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) {
      throw new BadRequestException('No fields to update');
    }

    const session = await this.sessionRepository.update(id, {
      isPendingSyncReset: dto.isPendingSyncReset,
    });

    return mapSession(session);
  }

  async delete(auth: AuthDto, id: string): Promise<void> {
    await this.requireAccess({ auth, permission: Permission.AuthDeviceDelete, ids: [id] });
    await this.sessionRepository.delete(id);
  }

  async deleteAll(auth: AuthDto): Promise<void> {
    const userId = auth.user.id;
    const currentSessionId = auth.session?.id;
    await this.sessionRepository.invalidateAll({ userId, excludeId: currentSessionId });

View on GitHub (pinned to 199723261c)

Solutions

  1. Include at least one defined field in the PATCH body (e.g. isPendingSyncReset: true).
  2. On the client, short-circuit the request when no fields changed rather than sending an empty body.
  3. If your DTO only has isPendingSyncReset, ensure the value is a boolean and not undefined.

Example fix

// before - empty body
await api.updateSession(id, {});
// after
await api.updateSession(id, { isPendingSyncReset: true });
Defensive patterns

Strategy: validation

Validate before calling

function pickDefined<T extends object>(obj: T): Partial<T> {
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;
}

const patch = pickDefined(dto);
if (Object.keys(patch).length === 0) {
  // nothing to do; skip the request entirely
  return;
}
await sessionApi.update(id, patch);

Type guard

const hasDefinedField = (dto: object): boolean =>
  Object.values(dto).some((v) => v !== undefined);

Try / catch

try {
  await sessionApi.update(id, dto);
} catch (e) {
  if (e instanceof BadRequestException && /no fields/i.test(e.message)) {
    // benign: nothing changed; ignore
    return;
  } else throw e;
}

Prevention

When it happens

Trigger: PATCH /sessions/:id with a body like {} or { isPendingSyncReset: undefined } — i.e. every field either omitted or explicitly undefined. Sending null does NOT count as undefined, so { isPendingSyncReset: null } would pass this check.

Common situations: Frontend sending a generic save form with no changed fields, client diff logic that strips unchanged values to undefined, or a misconfigured partial update that omits all keys.

Related errors


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