* fix(models): disk fallback when scan_cache_dir raises WinError 448 (#128)
plan-01 fix-sequence step 2. On Windows, huggingface_hub's scan_cache_dir()
raises WinError 448 "untrusted mount point"; the three call sites in
setup/models.py swallowed it and reported "not cached", so the app
re-downloaded models it already had — looping 5× and giving up (#117/#118).
- _is_cached_on_disk / _scan_cache_on_disk: walk the canonical HF layout
<cache>/models--<org>--<name>/snapshots/<rev>/ directly (honours
HF_HUB_CACHE/HF_HOME, so a relocated models dir works too).
- is_cached / list_models / recommendations now fall back to the disk scan
when scan_cache_dir() raises. An empty snapshot dir is not counted.
Symlink-disable env + local_dir_use_symlinks=False were already shipped
(main.py, setup/download.py); this closes the remaining failure path.
Tests (TDD, fail-before/pass-after): tests/test_hf_cache_fallback.py (4).
No regression on the non-Windows path (fallback only triggers on raise).
Closes#117, #118. Addresses #128 (#64 configurable-dir is the follow-up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(models): probe HF /hub subdir + close scandir handle (bot review)
Addresses #137 review:
- CodeRabbit (critical): hf_cache_dir() returns HF_HOME when HF_HUB_CACHE is
unset, but repos live under $HF_HOME/hub/models--…. Added _hub_cache_roots()
so the WinError-448 fallback probes both <dir> (HF_HUB_CACHE-set case) and
<dir>/hub (HF_HOME-only case); previously it could miss the cache and
re-download. Regression test added (HF_HOME-only layout).
- Greptile: wrap os.scandir() in `with` so the dir handle closes even when
any() short-circuits (avoids handle leaks on repeated /models polls).
- CodeQL: drop unused `os` import in the test.
5 tests pass, incl. -W error::ResourceWarning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>