d91beef0fd314250d8d9b94de86dfea019a8bd96
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d6f24dafd5 |
feat(hardening): six recurrence guards from the closed-issue-history audit (#1141)
* feat(hardening): six recurrence guards from the closed-issue-history audit An agent audit swept every closed issue, clustered the error classes, and checked each for fix + regression test + upgrade/reinstall survival. Six of the "fixed but fragile" gaps are closed here; each guard has a regression test in tests/test_recurrence_hardening.py (9 tests). 1. Evict-then-load (class 1, ~90 issues): a plain TTS load on a tight unified-memory box could still be OS-killed — the dub path frees memory before ASR loads (#1119) but nothing did before a TTS load. _make_room_before_tts_load() releases the idle capture-ASR model, clone prompts, and allocator caches when free RAM < the unified headroom. Deliberately NOT admission control: the #1111 decision (advisory-only, never refuse a load on an estimate) stands; this only does earlier what idle reclaim does later, and roomy machines skip it entirely. 2. Honest SIGKILL attribution (class 1): crashCauseHint() says "the OS ran out of memory (RAM)" for signal 9 instead of guessing VRAM on machines that have none. VRAM guidance kept for real GPU aborts (signal 6 etc.). 3. Clone-kind save sanitize (class 3, recurred 3x): the server-side instruct heal was gated to design-kind; a clone profile saved by any bypassing client could persist prose that 400s on every use. profiles.py now sanitizes both kinds at the single choke point. 4. Stale user_env validation (class 5): ~/.config/omnivoice/env is inherited verbatim by reinstalls; path-valued keys (OMNIVOICE_CACHE_DIR/DATA_DIR) that don't exist and can't be created are dropped for the run with a loud log line (file untouched — replugging the drive restores the setting). The two #480 precedence tests updated to use creatable paths (they test precedence, not path validity). 5. omni_ui schema guard (class 6): sanitizeOmniUi() whitelists + shape-checks every persisted field before restore — one malformed field used to throw mid-restore and silently discard everything after it, and every future field re-opened the #1067 class. Includes a lockstep test failing when useAppData reads a field missing from the schema. 6. safe_replace EXDEV helper (class 7): os.replace across devices raises EXDEV (the Windows D:-drive Errno 18/22 class); utils/fsops.safe_replace degrades to copy+fsync+replace. Adopted at the two cross-directory movers (log rotation, persona restore); temp-sibling writers stay on os.replace. Plus: the generate timeout scales with text length (class 4's 503 wave — +1s per 40 chars past the first 1200, env floor respected), so long texts on slow hardware stop dying at exactly 300s with a "set an env var" remedy. Deliberately NOT done, with reasons: - ASR auto-promotion to the crash-isolated engine after a wedge: the code records an explicit owner rule against silent engine switching (asr_backend.py "we never switch engines automatically") — flagged to the owner instead of overridden. - Rust items (webview cache-clear unit test, crash-marker versioning across updates): deferred to their own PR — the local cargo target was reclaimed for disk space, so they can't be verified locally right now. Full suite: 2999 backend + 1243 frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(changelog): correct PR ref to #1141 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(hardening): review round — reclaim at the shared load boundary, write-probe path validation Both Greptile P1s were real: - "Startup preload skips reclaim": _make_room_before_tts_load() ran only in get_model(); preload_model() calls _load_model_with_timeout() directly, so a memory-tight machine was protected on demand loads but could still be OS-killed during the startup preload — the exact window the guard exists for. The reclaim now lives in _load_model_with_timeout(), the boundary both callers share. - "Read-only paths pass validation": an existing directory on a read-only mount passes makedirs+isdir but fails on first real use, so the stale setting survived validation only to break downloads later. The check now probes actual write capability (create+delete a probe file). New test with a chmod-0o500 dir (skipped under root, where the probe cannot fail). - CodeQL: the two intentional best-effort excepts in fsops.py now carry their explanatory comments. Full suite: 3000 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a245d96684 |
fix(settings): make in-app models dir authoritative over launcher-injected env (#480) (#483)
Changing the model download location in Settings had no effect: after the prompted restart, new downloads still went to the old folder and "Effective location" stayed stuck on it. Two stores hold the models dir. The in-app Settings panel writes the new path to the durable per-user env file (`~/.config/omnivoice/env`, OMNIVOICE_CACHE_DIR), but the desktop launcher injects the OLD value from its own Tauri config into the backend's environment before startup — and main.py loaded the per-user file with `override=False`, so the launcher's stale value always won. main.py then maps OMNIVOICE_CACHE_DIR → HF_HOME/HF_HUB_CACHE/TORCH_HOME, pointing downloads at the old dir; `_effective_models_dir()` reads that live env, so the UI faithfully reported the old path as if the change had failed. Fix: load the per-user env file with override so it beats launcher-injected defaults — restoring this file's documented "values written here take effect on the next backend launch" contract. Centralized as `user_env.load_into_environ()` (the file is the in-app Settings source of truth) and called from main.py. Both keys this file can hold (OMNIVOICE_CACHE_DIR, HF_ENDPOINT) are the user's explicit Settings choice and should beat the launcher default, so the override is correct for both (this also fixes the same latent bug for a Settings-set HF mirror). HF_TOKEN isn't launcher-injected, so its behavior is unchanged. Follow-up (separate PR): add a Tauri `set_models_dir` command so the launcher's config.json stays in sync, covering the reset-to-default edge too. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |