fix(stt): verify cached model checksum - #239
Conversation
|
Warning Review limit reached
Next review available in: 3 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe model manager now validates cached and downloaded model files against expected SHA-256 hashes. Invalid files are quarantined with a ChangesModel cache validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
89e07a8 to
545043d
Compare
594c29f to
597f664
Compare
597f664 to
ac1bce7
Compare
EtienneLescot
left a comment
There was a problem hiding this comment.
Both holes are real. The second is the better catch: if (await areModelsPresent(opts.baseDir)) continue; gated the entire loop on the whisper file existing, so a second entry in STT_MODELS would silently never download. And verifying before the final rename rather than after is plainly right — main's version parks a corrupt file at the live model path on its way to throwing.
What I can't take is the quarantine. rename(tmp, filePath) is already atomic, so .bad closes no window; it only destroys the user's only copy before we know a replacement is obtainable. Offline, HF 5xx, disk full — main left a model behind, this leaves none, plus a stranded 264 MB file nothing in this tree ever deletes.
The failure mode I'd want covered before merge is upstream digest drift; details inline. Short version: the model URL goes through resolve/main, so the pin is a bet, and this PR converts "breaks new installs" into "invalidates every existing cache at once."
Rebase note: main has moved 30 commits since your base, including an STT status rework, but it only added completedSec/totalSec to SttStatusEvent and modelManager.ts never touches that type — you're clean there. One UX consequence: prepare() emits phase: "model" before ensureModels, which the renderer maps to mediaStage.downloadingModel, so the new cache hash renders as "Downloading speech model" for a second. Worth a separate beat or a reworded string.
There was a problem hiding this comment.
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 `@electron/stt/modelManager.test.ts`:
- Around line 71-93: Add a test in the same suite as the existing ensureModels
tests that makes the fetcher return content whose digest does not match
STT_MODELS.whisper.files[0].expectedSha256. Assert ensureModels rejects, the
downloaded content is written to `${paths.whisper}.bad`, and the original
paths.whisper file does not exist; restore any temporarily modified checksum in
cleanup.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7fb9e341-a059-4dd7-a1c5-e7ab914906c6
📒 Files selected for processing (2)
electron/stt/modelManager.test.tselectron/stt/modelManager.ts
The cache path never checked the digest it carries: ensureFile returned as soon as the file existed and was non-empty, so a model truncated by a full disk or scrambled by a bad sector was handed straight to whisper.cpp, and the only symptom the user got was garbage captions. ensureModels also bailed out on areModelsPresent before touching the loop body, which made the check about "is any model present" rather than "is this model present" — the moment a second entry joins STT_MODELS it would silently never download. Verifying the cache means an existing install can now fail the check on a file it has been happily using, so what happens next matters more than it used to. The stale copy stays exactly where it is while the replacement downloads: the final rename is already atomic, so moving it aside first closes no window, while costing the user their working model if the replacement never lands — offline, HuggingFace 5xx, no space — and stranding 264 MB that nothing ever reaps. If the replacement mismatches too, the download is discarded and the original bytes are kept, because a double mismatch is precisely when those bytes are worth looking at. That cleanup is guarded: a Windows AV scanner still holding the handle raises EPERM, and the bare errno would escape in place of the mismatch message that actually tells the user what went wrong. The URL now resolves through a pinned commit instead of resolve/main. A mutable branch pointer made the recorded digest a bet, and a re-upload used to break only new installs; with the cache verified on every start it would invalidate every installed cache at once. The pinned revision was checked against HuggingFace's paths-info API: its LFS oid for ggml-small-q8_0.bin is the digest already in the file, byte for byte. Bumping the model now means bumping the revision and the digest together.
EtienneLescot
left a comment
There was a problem hiding this comment.
Threads cleared, including CodeRabbit's, which was reviewing the quarantine this branch removed.
Approving. The quarantine was the real problem: renaming the user's only copy away before knowing a replacement was obtainable turned an offline retry into no model at all. Verification now happens on the .partial and the existing atomic rename does the displacing. I checked the URL pin against the live registry myself — resolve/5359861c… serves 49c8fb02…f779f, byte-identical to the digest already in the file, so it is an invariant now rather than a bet on a branch pointer.
Summary
Related issue
No linked issue; found by corrupting a non-empty cached model and exercising startup.
Type of change
Release impact
Desktop impact
Screenshots / video
Not applicable; model-cache integrity behavior.
Testing
Summary by CodeRabbit