Skip to content

fix(windows): HF cache disk-fallback for WinError 448 (plan-01, closes #117 #118) - #137

Merged
debpalash merged 2 commits into
mainfrom
002-windows-model-storage
May 29, 2026
Merged

fix(windows): HF cache disk-fallback for WinError 448 (plan-01, closes #117 #118)#137
debpalash merged 2 commits into
mainfrom
002-windows-model-storage

Conversation

@debpalash

@debpalash debpalash commented May 29, 2026

Copy link
Copy Markdown
Owner

plan-01 fix-sequence step 2Closes #117, #118. Addresses #128.

On Windows, huggingface_hub.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, leaving the app unusable (#117/#118).

Fix

A direct-filesystem fallback over the canonical HF layout <cache>/models--<org>--<name>/snapshots/<rev>/:

  • _is_cached_on_disk() / _scan_cache_on_disk() — honour HF_HUB_CACHE/HF_HOME, so a relocated models dir works too
  • is_cached() / list_models() / recommendations() fall back to it when scan_cache_dir() raises
  • an empty snapshot dir (interrupted download) is correctly not counted

The symlink-disable env (HF_HUB_DISABLE_SYMLINKS=1) and local_dir_use_symlinks=False (steps 1) already shipped in main.py/setup/download.py; this closes the remaining failure path.

Cross-platform parity

The fallback only triggers when scan_cache_dir() raises — macOS/Linux behaviour is unchanged.

Tests (TDD, fail-before/pass-after — Constitution V)

tests/test_hf_cache_fallback.py (4): cached repo detected when scan raises, uncached not, empty snapshot not counted, disk scan reports size/files. Targeted regression set (model/cache/setup): 23 passed, 0 failed.

Remaining cluster item

#64 (configurable models directory) builds on the existing OMNIVOICE_CACHE_DIR/HF_HOME plumbing — a Settings field + startup read — tracked as the follow-up within the v0.3.0 line.

Spec/plan/tasks in specs/002-windows-model-storage/.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved model cache detection on Windows with a filesystem fallback to avoid unnecessary re-downloads when standard cache inspection fails.
  • Tests

    • Added regression tests covering cached/uncached models, empty snapshot handling, and alternate cache-root scenarios.
  • Documentation

    • Added spec, plan, and task docs describing the Windows cache-fallback behavior and verification plan.

Review Change Stack

)

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>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 05fda8f3-d111-491e-b883-e381bd876b17

📥 Commits

Reviewing files that changed from the base of the PR and between 893857d and 0e14611.

📒 Files selected for processing (2)
  • backend/api/routers/setup/models.py
  • tests/test_hf_cache_fallback.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/api/routers/setup/models.py

📝 Walkthrough

Walkthrough

Adds a filesystem-based fallback for Hugging Face cache detection when scan_cache_dir() fails (WinError 448): on-disk cache scanning helpers, wired fallback paths in is_cached(), /models, and /setup/recommendations, plus spec/docs and regression tests.

Changes

Windows HF Cache Fallback

Layer / File(s) Summary
Specification & TDD Plan
specs/002-windows-model-storage/plan.md, specs/002-windows-model-storage/spec.md, specs/002-windows-model-storage/tasks.md
Requirements, plan, and tasks describing the WinError 448 fallback, endpoint coverage, empty-snapshot handling, and three-phase TDD approach.
HF Cache Filesystem Helpers
backend/api/routers/setup/models.py (lines 127–211)
Helpers to map repo IDs to HF models--... directory names, probe $HF_HUB_CACHE / $HF_HOME/hub, detect cached revisions by inspecting snapshots/<rev>/, and scan the cache root to compute per-repo file counts and sizes.
Exception Path Integration & Fallback Wiring
backend/api/routers/setup/models.py (lines 222–226, 278–281, 375–378)
Wires the on-disk helpers into exception paths: is_cached() falls back to disk check on scan failure; /models endpoint uses on-disk scan to populate installed/size fields when scanning fails; /setup/recommendations derives cached_ids from on-disk scan keys on failure.
Regression Tests for Cache Fallback
tests/test_hf_cache_fallback.py
Tests emulate scan_cache_dir() raising WinError 448 and validate that cached repos are detected from the filesystem, uncached repos are not, on-disk scanner reports file/size, HF_HOME-only layout is supported, and empty snapshots are not counted as cached.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers the bug context, fix approach, cross-platform parity, tests, and remaining work, but does not include the structured template sections (Summary/Changes/Type/Testing/Checklist). Use the repository's PR description template to ensure all required sections are present and improve consistency and clarity for reviewers.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a HuggingFace cache disk-fallback for WinError 448 on Windows.
Linked Issues check ✅ Passed The code changes implement all stated objectives: filesystem fallback functions (_is_cached_on_disk, _scan_cache_on_disk) that respect HF_HUB_CACHE/HF_HOME, fallback integration in is_cached/list_models/recommendations, empty snapshot handling, and regression tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing WinError 448 cache detection failures: backend fallback implementation, documentation of the fix (specs, plan, tasks), and targeted regression tests. No out-of-scope changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 002-windows-model-storage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread tests/test_hf_cache_fallback.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/api/routers/setup/models.py`:
- Around line 132-141: The fallback in _is_cached_on_disk currently builds snaps
under hf_cache_dir() directly, which misses the hub subdir when only HF_HOME is
set; change the snaps root to use the hub cache root (i.e., derive the hub root
as hf_cache_dir() plus the "hub" subdirectory or reuse huggingface_hub's
HF_HUB_CACHE constant) and then join _repo_dir_name(repo_id) and "snapshots" (so
use a hub_root = os.path.join(hf_cache_dir(), "hub") or equivalent before
os.path.join(hub_root, _repo_dir_name(repo_id), "snapshots") in
_is_cached_on_disk).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c7802c10-2b29-409c-9299-0e2350d0f982

📥 Commits

Reviewing files that changed from the base of the PR and between 8162f52 and 893857d.

📒 Files selected for processing (5)
  • backend/api/routers/setup/models.py
  • specs/002-windows-model-storage/plan.md
  • specs/002-windows-model-storage/spec.md
  • specs/002-windows-model-storage/tasks.md
  • tests/test_hf_cache_fallback.py

Comment thread backend/api/routers/setup/models.py Outdated
@greptile-apps

greptile-apps Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a direct-filesystem fallback for the three scan_cache_dir() call sites in setup/models.py so that Windows users who hit WinError 448 "untrusted mount point" no longer see cached models reported as missing and re-downloaded in a loop (#117/#118).

  • Core fix: _is_cached_on_disk() and _scan_cache_on_disk() walk the canonical HF layout; both are wired into the except blocks of is_cached, list_models, and recommendations.
  • Directory resolution: _hub_cache_roots() probes both <base> and <base>/hub, correctly handling all three env-var configurations and addressing the previously-reported off-by-one issue.
  • Tests: Five targeted regression tests cover all acceptance criteria; the fallback only activates when scan_cache_dir() raises, leaving macOS/Linux behaviour unchanged.

Confidence Score: 5/5

Safe to merge — the fallback only activates when scan_cache_dir() raises, leaving macOS/Linux untouched, and the new code degrades gracefully on any filesystem error.

The core logic is correct and well-tested. The dual-root probing in _hub_cache_roots() correctly handles all three env-var configurations. Two minor suggestions are refinements, not blockers.

No files require special attention.

Important Files Changed

Filename Overview
backend/api/routers/setup/models.py Adds filesystem fallback helpers and wires them into all three scan_cache_dir call sites; previously-flagged off-by-one directory issue resolved by _hub_cache_roots().
tests/test_hf_cache_fallback.py Five regression tests covering cached/uncached detection, HF_HOME-only path, empty snapshot dir, and disk scan size/file reporting.
specs/002-windows-model-storage/tasks.md Task tracking doc; T005 full-backend-suite gate still open (unchecked).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["is_cached / list_models / recommendations called"] --> B["scan_cache_dir()"]
    B -->|success| C["Use HF CacheInfo"]
    B -->|raises WinError 448| D["_hub_cache_roots() → base + base/hub"]
    D --> E["_is_cached_on_disk() or _scan_cache_on_disk()"]
    E -->|files found| F["Report cached"]
    E -->|no files| G["Report not cached"]
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "fix(models): probe HF /hub subdir + clos..." | Re-trigger Greptile

Comment thread backend/api/routers/setup/models.py Outdated
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Model installation fails with [Errno 22] Invalid argument and scan_cache_dir [WinError 448] on Windows

2 participants