BerriAI/litellm · error · HTTPException

`blocked_user_list` must be set as a list. Filepaths can't b

Error message

`blocked_user_list` must be set as a list. Filepaths can't be updated.

What it means

litellm.blocked_user_list can be either a Python list of user ids or a path to a file of ids. POST /customer/unblock only supports the list form - it calls list.remove() per id - so when the blocklist was loaded from a filepath string the endpoint refuses with HTTP 500: backtick-blocked_user_listbacktick must be set as a list, filepaths cannot be updated.

Source

Thrown at litellm/proxy/management_endpoints/customer_endpoints.py:242

                "error": "Blocked user check was never set. This call has no effect."
                + CommonProxyErrors.missing_enterprise_package_docker.value
            },
        )

    if (
        not any(isinstance(x, _ENTERPRISE_BlockedUserList) for x in litellm.callbacks)
        or litellm.blocked_user_list is None
    ):
        raise HTTPException(
            status_code=400,
            detail={"error": "Blocked user check was never set. This call has no effect."},
        )

    if isinstance(litellm.blocked_user_list, list):
        for id in data.user_ids:
            litellm.blocked_user_list.remove(id)
    else:
        raise HTTPException(
            status_code=500,
            detail={"error": "`blocked_user_list` must be set as a list. Filepaths can't be updated."},
        )

    return {"blocked_users": litellm.blocked_user_list}


def new_budget_request(data: NewCustomerRequest) -> BudgetNewRequest | None:
    """
    Return a new budget object if new budget params are passed.
    """
    budget_params: Final = BudgetNewRequest.model_fields.keys()
    budget_kv_pairs: Final = {}

    # Get the actual values from the data object using getattr
    for field_name in budget_params:
        if field_name == "budget_id":
            continue

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Switch the proxy to a list-based blocked_user_list in config.yaml and restart, then use the API
  2. Or edit the file directly (remove the id) and restart/reload the proxy - the API cannot mutate files
  3. Standardize on one workflow: file-based (manual edits) or list-based (API), never mixed

Example fix

# before
litellm_settings:
  blocked_user_list: /etc/litellm/blocked_users.txt

# after
litellm_settings:
  blocked_user_list: ["user-1", "user-2"]
Defensive patterns

Strategy: validation

Validate before calling

def unblock_supported_via_api(blocked_user_list: object) -> bool:
    return isinstance(blocked_user_list, list)

Try / catch

try:
    r = httpx.post(f"{base}/customer/unblock", json={"user_ids": ids}, headers=headers)
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 500 and "must be set as a list" in e.response.text:
        raise RuntimeError("blocklist is file-backed; edit the file and reload instead of the API") from e
    raise

Prevention

When it happens

Trigger: Configuring litellm_settings.blocked_user_list: "/etc/litellm/blocked_users.txt" (any string path) and then calling POST /customer/unblock to remove ids via the API.

Common situations: Large orgs load big blocklists from files for manageability, then admins try to unblock individual users via the API; enterprise UI uploads can also land as file-backed lists.

Related errors


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