{"record":{"id":"4d2e3699e863c1ce","repo":"immich-app/immich","slug":"admin-status-can-only-be-changed-by-another-admin","errorCode":null,"errorMessage":"Admin status can only be changed by another admin","messagePattern":"Admin status can only be changed by another admin","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/user-admin.service.ts","lineNumber":60,"sourceCode":"    await this.eventRepository.emit('UserSignup', {\n      notify: !!notify,\n      id: user.id,\n      password: userDto.password,\n    });\n\n    return mapUserAdmin(user);\n  }\n\n  async get(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {\n    const user = await this.findOrFail(id, { withDeleted: true });\n    return mapUserAdmin(user);\n  }\n\n  async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise<UserAdminResponseDto> {\n    const user = await this.findOrFail(id, {});\n\n    if (dto.isAdmin !== undefined && dto.isAdmin !== auth.user.isAdmin && auth.user.id === id) {\n      throw new BadRequestException('Admin status can only be changed by another admin');\n    }\n\n    if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {\n      await this.userRepository.syncUsage(id);\n    }\n\n    if (dto.email) {\n      const duplicate = await this.userRepository.getByEmail(dto.email);\n      if (duplicate && duplicate.id !== id) {\n        this.logger.debug('Email already in use by another account');\n        throw new BadRequestException('Email is not available');\n      }\n    }\n\n    if (dto.storageLabel) {\n      const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);\n      if (duplicate && duplicate.id !== id) {\n        throw new BadRequestException('Storage label already in use by another account');","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/user-admin.service.ts#L42-L78","documentation":"Thrown (as BadRequestException) by UserAdminService.update when dto.isAdmin is defined, differs from the acting admin's current isAdmin flag, and the target id equals the acting admin's own id. This prevents an admin from demoting (or re-promoting) themselves and thereby locking the instance out of its last admin.","triggerScenarios":"PUT /admin/users/:id where :id is the caller's own id and dto.isAdmin is set to a value different from their current admin flag.","commonSituations":"Admin tries to demote themselves via the UI; automation bulk-toggles isAdmin and includes the actor; self-toggle through a generic update form.","solutions":["Have a second admin perform the role change on this user.","Omit isAdmin from the payload when editing your own account.","If you are the only admin, promote another user first, then have them change your flag."],"exampleFix":"// before (self demote)\nawait usersApi.update(myId, { isAdmin: false });\n// after (another admin does it)\nawait usersApi.update(myId, { isAdmin: false }); // called by otherAdminAuth, not self","handlingStrategy":"validation","validationCode":"function isSelfAdminToggle(authId: string, id: string, dto: UserAdminUpdateDto): boolean {\n  return id === authId && dto.isAdmin !== undefined && dto.isAdmin !== auth.currentIsAdmin;\n}\nif (isSelfAdminToggle(auth.user.id, id, dto)) { /* block the action in the UI */ }","typeGuard":null,"tryCatchPattern":"try { await usersApi.update(id, dto); }\ncatch (e) {\n  if (e instanceof BadRequestException && /Admin status can only be changed/.test(e.message)) {\n    // route through another admin or drop isAdmin from the payload\n  }\n}","preventionTips":["Disable the 'isAdmin' toggle in the UI for the current user's own row.","Strip isAdmin from self-update payloads client-side.","Maintain at least two admins so role changes never deadlock."],"tags":["users","authorization","admin","validation","bad-request"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}