{"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":"exception","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/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/session.service.ts#L40-L76","documentation":"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.","triggerScenarios":"PUT /session/:id with an empty body or a body where the only fields are explicitly undefined (e.g. JSON {}).","commonSituations":"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.","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."],"exampleFix":"// before\nawait api.updateSession(id, {});\n// after\nawait api.updateSession(id, { isPendingSyncReset: true });","handlingStrategy":"validation","validationCode":"const changed = Object.values(dto).filter((v) => v !== undefined);\nif (changed.length === 0) {\n  throw new Error('Nothing to update: provide at least one field');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.updateSession(id, dto);\n} catch (e) {\n  if (e.status === 400 && e.message === 'No fields to update') {\n    // treat as no-op and continue\n  } else {\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["session","validation","empty-payload","api"],"backgroundTag":"empty-required-field","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}