{"record":{"id":"a967fe74b30f4bc8","repo":"invoke-ai/InvokeAI","slug":"str-e-valueerror-from-user-service-delete-e-g","errorCode":null,"errorMessage":"str(e) (ValueError from user service delete, e.g. LastAdministratorError)","messagePattern":"str\\(e\\) \\(ValueError from user service delete, e\\.g\\. LastAdministratorError\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"invokeai/app/api/routers/auth.py","lineNumber":692,"sourceCode":"    if user_id == SYSTEM_USER_ID:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=SYSTEM_USER_PROTECTED_DETAIL,\n        )\n\n    # Prevent deleting the last active admin. Same wording as the service backstop: this\n    # pre-check can lose a race and let the service reject the delete instead, and one\n    # endpoint should not report one condition two different ways.\n    if user.is_admin and user.is_active and user_service.count_admins() <= 1:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST,\n            detail=LAST_ADMIN_DETAIL,\n        )\n\n    try:\n        user_service.delete(user_id)\n    except ValueError as e:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e\n\n    # A deleted user must lose live access just like a deactivated one.\n    ApiDependencies.invoker.services.events.emit_user_access_changed(user_id=user_id, is_admin=False, is_active=False)\n\n\n@auth_router.patch(\"/me\", response_model=UserDTO)\ndef update_current_user(\n    request: Annotated[UserProfileUpdateRequest, Body(description=\"Profile fields to update\")],\n    current_user: CurrentUser,\n    http_request: Request,\n    response: Response,\n) -> UserDTO:\n    \"\"\"Update the current user's own profile.\n\n    To change the password, both ``current_password`` and ``new_password`` must\n    be provided. The current password is verified before the change is applied.\n\n    A password change signs out the account's *other* sessions: it bumps the","sourceCodeStart":674,"sourceCodeEnd":710,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/auth.py#L674-L710","documentation":"delete_user calls user_service.delete inside try/except ValueError; the service can raise ValueError (e.g. LastAdministratorError) if a race caused the pre-check to pass but the service still rejects the delete. The endpoint converts it to HTTP 400 with str(e) as the detail so the client gets a consistent 400 shape.","triggerScenarios":"DELETE /users/{id} where the last-admin pre-check passed but user_service.delete raises ValueError — typically a race where another admin was deactivated or the admin count changed between the check and the delete.","commonSituations":"Concurrent admin operations: one session deletes/deactivates the second admin while another session deletes what it believed was a non-last admin; stale UI state after another admin acted.","solutions":["Read the detail string to identify the specific ValueError (e.g. LastAdministratorError) and promote another active admin, then retry","Refresh user/admin list before retrying the delete to re-run the pre-check with fresh data","Avoid concurrent admin mutations on the same user set"],"exampleFix":"// before\ntry { await api.deleteUser(id) } catch (e) { /* unhandled 400 with service message */ }\n// after\ntry {\n  await api.deleteUser(id);\n} catch (e) {\n  if (e.detail?.includes('administrator')) { await api.updateUser(otherId, { is_admin: true }); await api.deleteUser(id); }\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await api.deleteUser(userId);\n} catch (e) {\n  if (e.response?.status === 400 && /administrator/i.test(e.response?.data?.detail ?? '')) {\n    await api.updateUser(otherAdminId, { is_admin: true });\n    await api.deleteUser(userId);\n  } else throw e;\n}","preventionTips":["Serialize admin-mutation operations to avoid races on the last-admin check","Re-fetch user/admin counts immediately before deletion","Handle the 400 detail string generically since it carries the service message"],"tags":["http-400","race-condition","user-service","last-admin"],"backgroundTag":"last-administrator-delete-blocked","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}