immich-app/immich · warning · BadRequestException

No fields to update

Error message

No fields to update

What it means

Session.update requires at least one defined field in the SessionUpdateDto. After the access check, it filters dto values that are not undefined; if none are set there is nothing to update and it throws this BadRequestException instead of issuing a no-op write.

Solutions

  1. Include at least one updatable field, e.g. { "isPendingSyncReset": true }.
  2. Skip the API call client-side when the diff of changed fields is empty.
  3. If the intent was a sync reset, send isPendingSyncReset explicitly.

Example fix

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

Strategy: validation

Validate before calling

const changed = Object.values(dto).filter((v) => v !== undefined);
if (changed.length === 0) {
  throw new Error('Nothing to update: provide at least one field');
}

Try / catch

try {
  await api.updateSession(id, dto);
} catch (e) {
  if (e.status === 400 && e.message === 'No fields to update') {
    // treat as no-op and continue
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: PUT /session/:id with an empty body or a body where the only fields are explicitly undefined (e.g. JSON {}).

Common situations: Client sends an empty PATCH/PUT payload after stripping nulls; a generic update wrapper serializes an object with no changed properties; frontend resets form to all-undefined before submit.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/caff23fb12560bcb. Report an issue: GitHub.

Appendix: 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 e55ac299a4)