invoke-ai/InvokeAI · error · HTTPException

str(e) (ValueError from user service update, e.g. LastAdmini

Error message

str(e) (ValueError from user service update, e.g. LastAdministratorError)

What it means

HTTP 400 raised by update_user when `user_service.update(...)` throws ValueError (auth.py:617), detail = `str(e)`. The route pre-checks unknown ids, system-user edits, and last-admin changes, but the service is authoritative and can still reject — e.g. a race where another request already demoted the last admin (LastAdministratorError), or a weak password under strict checking.

Source

Thrown at invokeai/app/api/routers/auth.py:617

        and before.is_active
        and (request.is_admin is False or request.is_active is False)
        and user_service.count_admins() <= 1
    ):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=LAST_ADMIN_DETAIL,
        )

    try:
        changes = UserUpdateRequest(
            display_name=request.display_name,
            password=request.password,
            is_admin=request.is_admin,
            is_active=request.is_active,
        )
        updated = user_service.update(user_id, changes, strict_password_checking=config.strict_password_checking)
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e

    # Authorization state changed — notify live connections (open sockets, the
    # session processor) so demotion/deactivation takes effect immediately
    # instead of persisting until reconnect or token expiry. A password reset bumps
    # the epoch without touching is_admin/is_active, and must drop the target's open
    # sockets too, so it is part of this condition.
    if (
        before.is_admin != updated.is_admin
        or before.is_active != updated.is_active
        or before.token_epoch != updated.token_epoch
    ):
        ApiDependencies.invoker.services.events.emit_user_access_changed(
            user_id=updated.user_id,
            is_admin=updated.is_admin,
            is_active=updated.is_active,
            token_epoch=updated.token_epoch,
        )

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Read the 400 `detail` — it is the service ValueError text (e.g. LastAdministratorError message).
  2. For last-admin races: promote a new admin first, then retry the demotion/deactivation.
  3. Use a password that meets strength rules or relax strict_password_checking for dev.
  4. Serialize admin user-management operations or retry idempotent updates after re-reading state.
  5. Re-GET the user to see current is_admin/is_active before re-patching.

Example fix

// before: single blind update
await api.patch(`/users/${id}`, {is_active:false});
// after: re-check state and retry once on 400
let r = await api.patch(`/users/${id}`, {is_active:false});
if (r.status === 400) {
  const u = await (await api.get(`/users/${id}`)).json();
  if (u.is_active) throw new Error(await r.text());
  r = await api.patch(`/users/${id}`, {is_active:false});
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Re-read current state immediately before updating to shrink the race window
const u = await (await fetch(`/api/v1/users/${id}`)).json();
if (u.is_admin && u.is_active) {
  const users = await (await fetch('/api/v1/users')).json();
  if (users.filter(x => x.is_admin && x.is_active).length <= 1) {
    throw new Error('Promote another admin first');
  }
}

Try / catch

try {
  return await api.patch(`/users/${id}`, changes);
} catch (e) {
  if (e.status === 400) {
    // detail is the service ValueError, e.g. LastAdministratorError text
    const fresh = await fetchUser(id);
    if (fresh) return api.patch(`/users/${id}`, changes); // one retry after re-check
    throw new Error(`Update rejected: ${e.detail}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: PATCH /users/{id} losing the last-admin race (another concurrent request demoted/deactivated the remaining admin between the pre-check and the update); password change failing strength validation with strict_password_checking; other service-level constraint violations.

Common situations: Two admins editing users simultaneously; automated scripts doing bulk password resets with weak passwords; TOCTOU races the friendly pre-checks cannot fully close (the comments note the pre-check can lose a race).

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/9f2eebca7c23ecdf. Report an issue: GitHub.