fix: detect hand-copied models in the cache root (#110) - #116
Open
solderzzc wants to merge 1 commit into
Open
Conversation
The scan accepted only two layouts: `models--org--name/snapshots/<hash>/` (huggingface-cli) and `models/org/name/` (Swift Hub). A user who copies a model folder into the cache by hand — the case in the report — produces neither, so the app silently refused to list it with no indication why. - Accept two further layouts: a `models--org--name` folder whose weights sit directly inside with no `snapshots/` level, and a folder copied in without the `models--` prefix at all (`org/name/` or bare `name/`). - Honour `HF_HUB_CACHE`, which huggingface-cli respects and this did not. Precedence now matches huggingface_hub: HF_HUB_CACHE, then HF_HOME/hub, then ~/.cache/huggingface/hub. - `snapshotDirectory()` now resolves through every supported layout. It previously returned a synthesised `…/snapshots/main` path that does not exist for a hand-copied model, which would have pointed SSD expert streaming at a missing directory rather than failing loudly. - Add `diagnoseUnrecognizedDirectories()`, reported once per distinct set from `ModelDownloadManager.refresh()`, so a folder that looks like a model but fails verification says why (missing weights, missing shard index, leftover .incomplete files) instead of vanishing. - Refresh on ModelsView appear. refresh() ran only at init, after a download and after a delete, so a folder copied in while the app was open stayed invisible until relaunch. - `delete()` only walks a hand-copied path that actually exists, and an empty or "models" id no longer resolves to the cache root. - Fix two stale comments: the header named the wrong cache directory, and scanDownloadedModels claimed a ModelCatalog filter it does not apply. Tests build real directory trees under a temporary cache root and cover every accepted layout, the rejections that must stay rejected, the diagnostics, and snapshotDirectory resolution. Also verified against a real 622MB model folder copied into a cache root exactly as described in the report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #110.
Root cause
ModelStorage.scanDownloadedModels()accepted only two layouts under the cache root:models--org--name/snapshots/<hash>/— the huggingface-cli cache layout, which additionally needsrefs/main, asnapshots/main, or exactly one snapshot directory to resolvemodels/org/name/— Swift Hub's materialized layoutCopying a model folder in by hand produces neither. The folder was skipped by the scan entirely, or reached
isDownloaded()and failed becauseresolvedSnapshotDirectory()returned nil — either way it silently did not appear, with nothing logged to say why.Changes
Accept the layouts users actually produce. In addition to the two above:
models--org--name/with the weights directly inside and nosnapshots/levelorg/name/or a barename/folder copied straight into the cache rootHonour
HF_HUB_CACHE. OnlyHF_HOMEwas read. Precedence now matcheshuggingface_hub:HF_HUB_CACHE(the hub directory itself), thenHF_HOME/hub, then~/.cache/huggingface/hub.Fix
snapshotDirectory(). It returned a synthesised…/snapshots/mainpath that does not exist for a hand-copied model.InferenceEngine.swift:370hands that path to SSD expert streaming, so a detected-but-unresolvable model would have misconfigured the streaming reader instead of failing loudly. It now resolves through every supported layout and falls back to the hub path only when nothing exists.Explain rejections.
diagnoseUnrecognizedDirectories()reports folders that contain aconfig.jsonbut did not make it into the scan, with the reason — missing weights, sharded weights with nomodel.safetensors.index.json, or leftover.incompletefiles.ModelDownloadManager.refresh()logs these once per distinct set, so repeated refreshes stay quiet.Rescan on view appear.
refresh()ran only at init, after a download, and after a delete. A folder copied in while the app was open stayed invisible until relaunch.Deletion safety.
delete()walks the directories associated with an id, so a hand-copied path is included only when it actually exists, and an empty or"models"id can no longer resolve to the cache root or the layout wrapper. Covered by tests.Also corrects two stale comments: the file header named
~/Library/Caches/huggingface/hub(wrong directory), andscanDownloadedModels()claimed to filter byModelCatalog.all, which it does not do.Tests
New
tests/SwiftBuddyTests/ModelStorageLayoutTests.swift— 17 tests building real directory trees under a temporary cache root (ModelStorage.cacheRootOverride, added as a test hook): every accepted layout including sharded, the rejections that must stay rejected, the diagnostic reasons,snapshotDirectoryresolution per layout, and deletion safety.Also verified against a real 622MB model folder copied into a cache root exactly as the report describes (
cp -RL <snapshot> <hub>/Qwen3.5-0.8B-oQ6): detected at the right size,config.jsonreadable, max context length parsed,snapshotDirectoryresolving to the copied folder, and no spurious diagnostics.Full suite: 129 tests across 10 suites, 0 failures.
PromptCacheTestsandModelLifecycleTestsabort underswift testwithFailed to load the default metallib— reproduced at HEAD without these changes, so pre-existing and environmental; suites were run individually.What is not verified here
The fix is verified at the
ModelStoragelayer, not through the SwiftBuddy UI — loading a model end-to-end in a test needs the Metal library the test bundle cannot find. The reporter's exact directory layout is also still unknown; this covers the plausible shapes rather than a confirmed one, and the new diagnostics mean the next such report arrives with the reason attached.🤖 Generated with Claude Code