BerriAI/litellm · error · HTTPException

Failed to update user

Error message

Failed to update user

What it means

At the end of _update_single_user_helper, a None response from the Prisma update (no row changed) turns into HTTP 400 {'error': 'Failed to update user'}. This typically means the update's where-clause matched nothing: a proxy_admin updating a non-existent user passes _check_user_update_authz (the 404 silent-create guard only fires for non-admins) and then the write is a no-op, returning None.

Source

Thrown at litellm/proxy/management_endpoints/internal_user_endpoints.py:1492

        )

        await _invalidate_user_spend_counter_if_changed(non_default_values)

        if "object_permission_id" in non_default_values:
            await _invalidate_cached_user_entitlement(
                user_id=non_default_values.get("user_id"),
                object_permission_ids=tuple(
                    permission_id
                    for permission_id in (
                        getattr(existing_user_row, "object_permission_id", None),
                        non_default_values.get("object_permission_id"),
                    )
                    if isinstance(permission_id, str)
                ),
            )

    if response is None:
        raise HTTPException(
            status_code=400,
            detail={"error": "Failed to update user"},
        )
    _strip_password_from_response(response)
    return response


def can_user_call_user_update(
    user_api_key_dict: UserAPIKeyAuth,
    user_info: LiteLLM_UserTable,
) -> bool:
    """
    Helper to check if the user has access to the key's info
    """
    if (
        user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value
        or user_api_key_dict.user_id == user_info.user_id
    ):

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Verify the target exists first (GET /user/list) and correct the user_id/user_email
  2. Create missing users via POST /user/new instead of relying on /user/update
  3. Inspect verbose proxy logs around the update to see the Prisma result if the id looks correct

Example fix

# before
POST /user/update -H 'Authorization: Bearer sk-admin' {"user_id": "ghost", "user_alias": "x"}
# 400 Failed to update user

# after
POST /user/new   -H 'Authorization: Bearer sk-admin' {"user_id": "ghost"}
POST /user/update -H 'Authorization: Bearer sk-admin' {"user_id": "ghost", "user_alias": "x"}  # 200
Defensive patterns

Strategy: try-catch

Validate before calling

import requests

def user_exists(base_url: str, headers: dict, user_id: str) -> bool:
    r = requests.get(f"{base_url}/user/list", headers=headers, timeout=10)
    r.raise_for_status()
    return any(u.get("user_id") == user_id for u in r.json().get("data", []))

Try / catch

except requests.HTTPError as e:
    body = e.response.text if e.response is not None else ""
    if e.response is not None and e.response.status_code == 400 and "Failed to update user" in body:
        # no row matched: verify existence, create via /user/new if intended, then retry once
        ...

Prevention

When it happens

Trigger: POST /user/update as proxy_admin with a user_id (or email resolving to no row) that does not exist in LiteLLM_UserTable; the prisma update call returning None because the filter matched zero rows.

Common situations: Admin upsert-style calls assuming /user/update creates missing users; users deleted mid-flight; identifiers that differ subtly from stored values (encoding, whitespace).

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/33c236f2a13c61c9. Report an issue: GitHub.