{"record":{"id":"267fa90ceb916aa3","repo":"invoke-ai/InvokeAI","slug":"cannot-remove-the-last-administrator","errorCode":null,"errorMessage":"Cannot remove the last administrator","messagePattern":"Cannot remove the last administrator","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"invokeai/app/api/routers/auth.py","lineNumber":603,"sourceCode":"        request.is_active is False or request.is_admin is True or request.password is not None\n    ):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=SYSTEM_USER_PROTECTED_DETAIL,\n        )\n\n    # Demoting or deactivating the last administrator is irreversible: authorization is\n    # derived from the database on every request, so the caller loses admin access\n    # immediately and no authenticated path back exists. It would also drop `has_admin()`\n    # to zero, which re-opens the unauthenticated `/auth/setup` endpoint to any caller.\n    # `delete_user` guards the same invariant.\n    if (\n        before.is_admin\n        and before.is_active\n        and (request.is_admin is False or request.is_active is False)\n        and user_service.count_admins() <= 1\n    ):\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=LAST_ADMIN_DETAIL,\n        )\n\n    try:\n        changes = UserUpdateRequest(\n            display_name=request.display_name,\n            password=request.password,\n            is_admin=request.is_admin,\n            is_active=request.is_active,\n        )\n        updated = user_service.update(user_id, changes, strict_password_checking=config.strict_password_checking)\n    except ValueError as e:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e\n\n    # Authorization state changed — notify live connections (open sockets, the\n    # session processor) so demotion/deactivation takes effect immediately\n    # instead of persisting until reconnect or token expiry. A password reset bumps","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/auth.py#L585-L621","documentation":"HTTP 400 raised by update_user when the change would demote or deactivate the last active administrator (auth.py:603). Authorization is derived from the database on every request, so removing the final admin would irreversibly lock everyone out of administration; LAST_ADMIN_DETAIL communicates this.","triggerScenarios":"PATCH /api/v1/users/{id} on the sole active admin with either \"is_admin\": false or \"is_active\": false, while `user_service.count_admins() <= 1`.","commonSituations":"Single-admin installs where the owner tries to demote themselves; cleanup scripts deactivating all users including the only admin; transferring admin rights by demoting before the new admin was promoted.","solutions":["Promote another user to admin first (PATCH {\"is_admin\": true}), then demote/deactivate the original admin.","Reorder bulk operations: grant new admins before removing old ones.","Skip the last active admin in cleanup scripts.","If already locked out at the DB level, restore from backup or fix is_admin flags directly in the users table as a last resort."],"exampleFix":"// before: demote before promoting a successor\nawait api.patch(`/users/${oldAdmin}`, {is_admin:false});\nawait api.patch(`/users/${newAdmin}`, {is_admin:true}); // 400: last admin\n// after: promote successor first\nawait api.patch(`/users/${newAdmin}`, {is_admin:true});\nawait api.patch(`/users/${oldAdmin}`, {is_admin:false});","handlingStrategy":"validation","validationCode":"async function canRemoveAdmin(id) {\n  const users = await (await fetch('/api/v1/users')).json();\n  const target = users.find(u => u.id === id);\n  const activeAdmins = users.filter(u => u.is_admin && u.is_active);\n  if (target?.is_admin && target.is_active && activeAdmins.length <= 1) {\n    return ['cannot demote/deactivate the last active administrator'];\n  }\n  return []; // [] => safe\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.patch(`/users/${id}`, {is_admin:false});\n} catch (e) {\n  if (e.status === 400 && e.detail.includes('last administrator')) {\n    throw new Error('Promote another admin before demoting this one');\n  }\n  throw e;\n}","preventionTips":["Always promote a successor admin before demoting or deactivating the current one","Count active admins before running bulk deactivation scripts","Exclude the last active admin from cleanup operations","Keep at least two active admins on production installs for safety"],"tags":["http-400","auth","admin","users","lockout"],"backgroundTag":"last-admin-protection","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}