From 4436678f7d7632a47e403f2139153e3f8a2ff8ba Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:22:18 +1000 Subject: [PATCH] fix(desktop): guard destroyed recovery windows (#37406) --- packages/desktop/src/main/index.ts | 3 ++- packages/desktop/src/main/unresponsive.ts | 3 ++- packages/desktop/src/main/window-state.ts | 29 +++++++++++++++++++++++ packages/desktop/src/main/windows.ts | 14 ++++------- 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 packages/desktop/src/main/window-state.ts diff --git a/packages/desktop/src/main/index.ts b/packages/desktop/src/main/index.ts index c21d77fc24..c7c1643092 100644 --- a/packages/desktop/src/main/index.ts +++ b/packages/desktop/src/main/index.ts @@ -33,6 +33,7 @@ import { type SidecarListener, } from "./server" import { setupAutoUpdater, showUpdaterDialog } from "./updater" +import { safeWebContentsURL } from "./window-state" import { getLastFocusedWindow, registerRendererProtocol, @@ -233,7 +234,7 @@ const main = Effect.gen(function* () { }) app.on("render-process-gone", (_event, webContents, details) => { - writeLog("window", "app render process gone", { url: webContents.getURL(), details }, "error") + writeLog("window", "app render process gone", { url: safeWebContentsURL(webContents), details }, "error") }) setRelaunchHandler(() => { diff --git a/packages/desktop/src/main/unresponsive.ts b/packages/desktop/src/main/unresponsive.ts index 1cc0924c3a..b5778d9e91 100644 --- a/packages/desktop/src/main/unresponsive.ts +++ b/packages/desktop/src/main/unresponsive.ts @@ -1,5 +1,6 @@ import type { BrowserWindow } from "electron" import { write as writeLog } from "./logging" +import { safeWindowURL } from "./window-state" const sampleInterval = 1000 const samplePeriod = 15000 @@ -46,7 +47,7 @@ export function createUnresponsiveSampler(win: BrowserWindow, name: string) { const message = [ "renderer unresponsive samples", `Window: ${name}`, - `URL: ${win.isDestroyed() ? "" : win.webContents.getURL()}`, + `URL: ${safeWindowURL(win)}`, ...entries.map((entry) => `<${entry[1]}> ${entry[0]}`), `Total Samples: ${total}`, ].join("\n") diff --git a/packages/desktop/src/main/window-state.ts b/packages/desktop/src/main/window-state.ts new file mode 100644 index 0000000000..3026b96b42 --- /dev/null +++ b/packages/desktop/src/main/window-state.ts @@ -0,0 +1,29 @@ +export const destroyedWindowURL = "" + +type WebContentsURLState = { + isDestroyed(): boolean + getURL(): string +} + +type WindowURLState = { + isDestroyed(): boolean + readonly webContents: WebContentsURLState +} + +export function safeWebContentsURL(webContents: WebContentsURLState) { + try { + if (webContents.isDestroyed()) return destroyedWindowURL + return webContents.getURL() + } catch { + return destroyedWindowURL + } +} + +export function safeWindowURL(win: WindowURLState) { + try { + if (win.isDestroyed()) return destroyedWindowURL + return safeWebContentsURL(win.webContents) + } catch { + return destroyedWindowURL + } +} diff --git a/packages/desktop/src/main/windows.ts b/packages/desktop/src/main/windows.ts index 693690c9c4..7a594b5302 100644 --- a/packages/desktop/src/main/windows.ts +++ b/packages/desktop/src/main/windows.ts @@ -13,6 +13,7 @@ import { getStore, removeStoreFile } from "./store" import { PINCH_ZOOM_ENABLED_KEY, WINDOW_IDS_KEY } from "./store-keys" import { createUnresponsiveSampler } from "./unresponsive" import { createWindowRegistry } from "./window-registry" +import { safeWindowURL } from "./window-state" const root = dirname(fileURLToPath(import.meta.url)) const rendererRoot = join(root, "../renderer") @@ -364,7 +365,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { errorCode, errorDescription, validatedURL, - currentURL: win.webContents.getURL(), + currentURL: safeWindowURL(win), isMainFrame, }, "error", @@ -386,12 +387,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { }) win.webContents.on("render-process-gone", (_event, details) => { sampler.stopAndFlush() - writeLog( - "window", - "renderer process gone", - { window: name, currentURL: win.webContents.getURL(), details }, - "error", - ) + writeLog("window", "renderer process gone", { window: name, currentURL: safeWindowURL(win), details }, "error") void show( "OpenCode window terminated unexpectedly", [`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? ""}`].join("\n"), @@ -399,12 +395,12 @@ function wireWindowRecovery(win: BrowserWindow, name: string) { ) }) win.on("unresponsive", () => { - writeLog("window", "renderer unresponsive", { window: name, currentURL: win.webContents.getURL() }, "error") + writeLog("window", "renderer unresponsive", { window: name, currentURL: safeWindowURL(win) }, "error") sampler.start() void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true) }) win.on("responsive", () => { - writeLog("window", "renderer responsive", { window: name, currentURL: win.webContents.getURL() }, "error") + writeLog("window", "renderer responsive", { window: name, currentURL: safeWindowURL(win) }, "error") sampler.stopAndFlush() }) win.webContents.on("console-message", (_event, level, message, line, sourceId) => {