agent: code-atomiser-fix — FirstRunPage unlisten on all exits (Race-9)

Move both Tauri listen() calls inside the try block with let-declared
unlisten handles. A throw on the second listen() previously leaked the
first subscription and the finally block could itself throw on an
undefined unlistenParakeet, masking the original error. Finally now
guards each handle before invoking it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 15:12:06 +01:00
parent 094b533ef2
commit 6aa6a434fb

View File

@@ -43,15 +43,23 @@
downloadProgress = 0;
downloadStartedAt = Date.now();
const unlisten = await listen("model-download-progress", (event) => {
downloadProgress = event.payload.percent;
});
const unlistenParakeet = await listen("parakeet-download-progress", (event) => {
downloadProgress = event.payload.percent;
});
// Tauri unlisten handles must be released on every exit path —
// declared outside the try so the finally can always call them,
// but assigned inside so a throw on the second listen() doesn't
// leak the first one. SettingsPage.svelte:864-880 is the
// long-form reference pattern (onMount + onDestroy).
let unlisten: (() => void) | undefined;
let unlistenParakeet: (() => void) | undefined;
try {
unlisten = await listen("model-download-progress", (event) => {
downloadProgress = event.payload.percent;
});
unlistenParakeet = await listen("parakeet-download-progress", (event) => {
downloadProgress = event.payload.percent;
});
if (modelId.startsWith("whisper-")) {
// backend's whisper_model_id accepts the full model id via its
// `other => ModelId::new(other)` fallback, so pass the id through
@@ -90,8 +98,11 @@
error = `Download failed: ${e}`;
downloading = false;
} finally {
unlisten();
unlistenParakeet();
// Guard against listen() throwing before assigning the unlisten
// handle — without these checks the finally itself would throw
// and mask the original error.
if (unlisten) unlisten();
if (unlistenParakeet) unlistenParakeet();
}
}