{"record":{"id":"caff23fb12560bcb","repo":"immich-app/immich","slug":"no-fields-to-update","errorCode":null,"errorMessage":"No fields to update","messagePattern":"No fields to update","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/session.service.ts","lineNumber":58,"sourceCode":"      expiresAt: dto.duration ? DateTime.now().plus({ seconds: dto.duration }).toJSDate() : null,\n      deviceType: dto.deviceType,\n      deviceOS: dto.deviceOS,\n      token: hashed,\n    });\n\n    return { ...mapSession(session), token };\n  }\n\n  async getAll(auth: AuthDto): Promise<SessionResponseDto[]> {\n    const sessions = await this.sessionRepository.getByUserId(auth.user.id);\n    return sessions.map((session) => mapSession(session, auth.session?.id));\n  }\n\n  async update(auth: AuthDto, id: string, dto: SessionUpdateDto): Promise<SessionResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.SessionUpdate, ids: [id] });\n\n    if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) {\n      throw new BadRequestException('No fields to update');\n    }\n\n    const session = await this.sessionRepository.update(id, {\n      isPendingSyncReset: dto.isPendingSyncReset,\n    });\n\n    return mapSession(session);\n  }\n\n  async delete(auth: AuthDto, id: string): Promise<void> {\n    await this.requireAccess({ auth, permission: Permission.AuthDeviceDelete, ids: [id] });\n    await this.sessionRepository.delete(id);\n  }\n\n  async deleteAll(auth: AuthDto): Promise<void> {\n    const userId = auth.user.id;\n    const currentSessionId = auth.session?.id;\n    await this.sessionRepository.invalidateAll({ userId, excludeId: currentSessionId });","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/session.service.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before - empty body\nawait api.updateSession(id, {});\n// after\nawait api.updateSession(id, { isPendingSyncReset: true });","handlingStrategy":"validation","validationCode":"function pickDefined<T extends object>(obj: T): Partial<T> {\n  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;\n}\n\nconst patch = pickDefined(dto);\nif (Object.keys(patch).length === 0) {\n  // nothing to do; skip the request entirely\n  return;\n}\nawait sessionApi.update(id, patch);","typeGuard":"const hasDefinedField = (dto: object): boolean =>\n  Object.values(dto).some((v) => v !== undefined);","tryCatchPattern":"try {\n  await sessionApi.update(id, dto);\n} catch (e) {\n  if (e instanceof BadRequestException && /no fields/i.test(e.message)) {\n    // benign: nothing changed; ignore\n    return;\n  } else throw e;\n}","preventionTips":["Short-circuit PATCH requests client-side when no field changed.","Diff form state against the original before building the DTO."],"tags":["session","validation","dto"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}