Repository navigation
[Robustness] Model download & switch: cancellable lifecycle, integrity checks, and failure recovery #102
Description
[Robustness] Model download & switch: cancellable lifecycle, integrity checks, and failure recovery
Umbrella ticket for local model download / load / switch robustness. Consolidates several
reported symptoms that share the same root causes.
Problem
Local model download and switching have no failure recovery. A stalled download, a
partially-written model, or a failed switch leaves the app in a state the user cannot escape:
a progress bar or spinner that never resolves, and a model offered as usable when its bytes
never landed.
Root causes (observed)
- Directory-exists = installed.
refreshDownloadedModelsmarks a model installed if its
folder exists (ModelManager.swift:888-905), with no completeness check. A killed or partial
download reads as installed and is offered for switching. - No download timeout; inconsistent load timeouts.
WhisperKit.downloadand the prewarm
compile have no timeout (ModelManager.swift:701-711, 1075-1102). Load timeouts are
inconsistent (60 s in one alert, 120 s in another) and are only discovered after long doomed
work. - Concurrency is not gated.
ModelManager.downloadModelhas an!isDownloadinglock
(:1024-1027), but the Models UI does not enforce it, and switching triggers a download as
a side effect (repair path). A second request fails only after a multi-minute load attempt:13:45:58 Switching to model: openai_whisper-large-v3_turbo 13:45:59 Loading ... from local folder 13:47:59 ERROR Model loading timed out after 120s 13:47:59 attemptWhisperModelRepairAndReload begin 13:47:59 ERROR downloadModel rejected: another download in progress current=parakeet-tdt-0.6b-v3 - Path / location mismatch. Parakeet downloads to
~/Library/Application Support/Pindrop/FluidInference/parakeet-coreml/..., but the app is
configured for~/Library/Application Support/FluidAudio/Models(AppCoordinator.swift:872-874).
Downloader and loader disagree on location → re-download / hang / timeout (see [Bug]: Parakeet v3 times out during load and startup #86, [Bug]: Speaker diarization download succeeds but validation checks legacy model directory #92). - Silent switch exits. Every branch of
switchToModel(named:)that returns without a
terminal event leaves the Models row in a permanent indeterminate spinner
(AppCoordinator.swift:7346, 7351, 7359, 7364, 7389, 7395; UI clear path only at
ModelsSettingsView.swift:117-122).
Workstreams
1. Cancellable, time-bounded download lifecycle
- Add a cancellation handle to
downloadModel; expose cancel/abort. - Add a watchdog/timeout around
WhisperKit.download, prewarm compile, and Parakeet/SenseVoice
fetches. On timeout: typed failure, clear state, leave the filesystem clean. - Adopt one consistent load-timeout policy (not 60 s here, 120 s there).
Acceptance: a stalled download fails within a bounded time with an error; cancel stops it and
cleans partial assets; isDownloading / currentDownloadModel cleared on every exit path.
2. Model integrity validation
- Replace directory-existence with a completeness check (required files / manifest) for
WhisperKit, Parakeet, and SenseVoice before "Installed" or switch is offered. - Reuse the diarization
isOfflineDiarizationReady()pattern.
Acceptance: a partial/corrupt folder is never reported as downloaded.
3. Startup reconciliation
- On launch, detect partial/corrupt model assets and offer verify / repair / delete instead of
presenting or selecting them.
Acceptance: installing after a killed mid-download leaves the app usable; the model shows not
installed (or "needs repair").
4. Locate the model where the loader expects it
- Make download and load use the same resolved directory; resolve the Parakeet
Pindrop/FluidInference/...
vsFluidAudio/Modelsmismatch. - Cross-check feature-model directories too (see [Bug]: Speaker diarization download succeeds but validation checks legacy model directory #92).
Acceptance: a completed download is found and loaded with no re-download; no second copy in
another Application Support directory.
5. Switch as explicit request/response
- Every
switchToModel(named:)path must post a terminal outcome (success or failure); no silent
return. On failure/no-op, clear pending UI state and surface an error/retry. - Replace the fire-and-forget
NotificationCenterrequest with a mechanism that carries completion.
Acceptance: switchingToModel is always cleared; every branch is UI-visible.
6. Prevent or queue concurrent downloads
- Disable/queue conflicting Download and switch actions while a download is running.
- Fail fast at request time if concurrency is unsupported — never after minutes of loading.
Acceptance: no user action can start a second download that only fails later.
7. Per-model management surface
- Retry / Cancel / Delete / Verify on the Models row for local models (Delete exists; add the rest).
8. Disk-usage accounting
- "ON DISK" sums catalog labels for every directory that exists, including partial ones
(ModelsSettingsView.swift:61-72). Make it reflect real installed bytes.
Related
- [Bug]: Parakeet v3 times out during load and startup #86 — Parakeet recognized as installed but load times out (same path/location + timeout root cause)
- [Bug]: Cannot download / use Whisper Base (English) or other Models #81 — Cannot download / use Whisper Base (corrupt/download handling)
- [Bug]: Speaker diarization download succeeds but validation checks legacy model directory #92 — diarization validation checks legacy directory name (same location-mismatch family)
- [Feature]: Add option to delete downloaded local helpers (not models) #90 — can't delete downloaded local helpers
- Catalog metadata accuracy (wrong sizes) — separate ticket
Key files
Packages/PindropShared/Sources/PindropSpeech/ModelManager.swiftPackages/PindropShared/Sources/PindropSpeech/Transcription/ParakeetEngine.swiftPindrop/UI/Settings/ModelsSettingsView.swiftPindrop/AppCoordinator.swift(switchToModel:7345,loadAndActivateModel:1673,
setActiveModel:1596,attemptWhisperModelRepairAndReload:1717)
Notes
Full technical diagnosis: notes/model-download-robustness.md.
Diariation already validates completeness; reuse that approach.
Activity