invoke-ai/InvokeAI · error · HTTPException

The system user cannot be deleted, deactivated, promoted to

Error message

The system user cannot be deleted, deactivated, promoted to administrator, or given a password

What it means

HTTP 400 raised by update_user when the request targets SYSTEM_USER_ID with is_active=false, is_admin=true, or a password (auth.py:587). The built-in system user owns all pre-multiuser content (boards, images, workflows, queue items), so deactivating it strands that content and promoting/giving it credentials is refused. SYSTEM_USER_PROTECTED_DETAIL is the friendly front for the `_assert_system_user_protected` service backstop.

Source

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

    """
    user_service = ApiDependencies.invoker.services.users
    config = ApiDependencies.invoker.services.configuration
    before = user_service.get(user_id)
    # Match `get_user`/`delete_user`, which 404 for an unknown id. Without this the request
    # falls through to the service's `ValueError("User ... not found")` and the route's
    # `except ValueError` reports it as a 400, contradicting this endpoint's own contract.
    if before is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")

    # The system user owns everything migrated from before multiuser support. Deactivating
    # it would strand that content: its queue items stop at the dequeue gate, and reads and
    # saves against system-owned media raise PermissionError. Promoting it or giving it a
    # password is refused for a different reason — see `_assert_system_user_protected`,
    # which is the backstop this friendly message fronts.
    if user_id == SYSTEM_USER_ID and (
        request.is_active is False or request.is_admin is True or request.password is not None
    ):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=SYSTEM_USER_PROTECTED_DETAIL,
        )

    # Demoting or deactivating the last administrator is irreversible: authorization is
    # derived from the database on every request, so the caller loses admin access
    # immediately and no authenticated path back exists. It would also drop `has_admin()`
    # to zero, which re-opens the unauthenticated `/auth/setup` endpoint to any caller.
    # `delete_user` guards the same invariant.
    if (
        before.is_admin
        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,

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Exclude the system user id from deactivation/promotion/password bulk operations.
  2. Leave the system user active and unprivileged — it is an internal ownership account, not a login.
  3. Create and manage real users via POST /users instead.
  4. Filter it client-side when enumerating users for edits.

Example fix

// before: blanket deactivate of every user
for (const u of users) await api.patch(`/users/${u.id}`, {is_active:false});
// after: skip the protected system user
for (const u of users) {
  if (u.id === SYSTEM_USER_ID) continue;
  await api.patch(`/users/${u.id}`, {is_active:false});
}
Defensive patterns

Strategy: validation

Validate before calling

const SYSTEM_USER_ID = 'system'; // exported by the SDK
function canPatchUser(id, changes) {
  if (id === SYSTEM_USER_ID &&
      (changes.is_active === false || changes.is_admin === true || changes.password != null)) {
    return ['system user cannot be deactivated, promoted, or given a password'];
  }
  return []; // [] => safe to PATCH
}

Type guard

function isForbiddenSystemUserChange(changes) {
  return changes.is_active === false ||
         changes.is_admin === true ||
         changes.password != null;
}

Try / catch

try {
  await api.patch(`/users/${id}`, changes);
} catch (e) {
  if (e.status === 400 && e.detail.includes('system user')) {
    console.warn('System user is protected; skipping');
  } else throw e;
}

Prevention

When it happens

Trigger: PATCH /api/v1/users/{SYSTEM_USER_ID} with any of: body containing "is_active": false, "is_admin": true, or a non-null "password".

Common situations: Admin trying to 'clean up' the odd system row that has no password and cannot log in; scripts that deactivate all non-admin accounts and sweep the system user into the batch; attempts to assign the system user a password to log in as it.

Related errors


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