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
- Include at least one defined field in the PATCH body (e.g. isPendingSyncReset: true).
- On the client, short-circuit the request when no fields changed rather than sending an empty body.
- 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
- Short-circuit PATCH requests client-side when no field changed.
- Diff form state against the original before building the DTO.
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
- Either `query` or `queryAssetId` must be set
- This endpoint can only be used with a session token
- Invalid albumId
- Invalid assetIds
- Invalid environment variables: \n - [${path}] ${issue.messa
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/caff23fb12560bcb.
Report an issue: GitHub.