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
- Include at least one updatable field, e.g. { "isPendingSyncReset": true }.
- Skip the API call client-side when the diff of changed fields is empty.
- 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
- Compute the diff of changed fields and skip the call when empty.
- Never send an all-undefined body.
- Unit-test update wrappers with empty payloads.
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
- Asset dimensions are not available for editing
- Crop action must be the first edit action
- Crop parameters are out of bounds
- Invalid cursor
- Invalid license key
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)