fix(desktop): guard destroyed recovery windows (#37406)

This commit is contained in:
Luke Parker
2026-07-17 11:22:18 +10:00
committed by GitHub
parent 08fb473735
commit 4436678f7d
4 changed files with 38 additions and 11 deletions
+2 -1
View File
@@ -33,6 +33,7 @@ import {
type SidecarListener, type SidecarListener,
} from "./server" } from "./server"
import { setupAutoUpdater, showUpdaterDialog } from "./updater" import { setupAutoUpdater, showUpdaterDialog } from "./updater"
import { safeWebContentsURL } from "./window-state"
import { import {
getLastFocusedWindow, getLastFocusedWindow,
registerRendererProtocol, registerRendererProtocol,
@@ -233,7 +234,7 @@ const main = Effect.gen(function* () {
}) })
app.on("render-process-gone", (_event, webContents, details) => { 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(() => { setRelaunchHandler(() => {
+2 -1
View File
@@ -1,5 +1,6 @@
import type { BrowserWindow } from "electron" import type { BrowserWindow } from "electron"
import { write as writeLog } from "./logging" import { write as writeLog } from "./logging"
import { safeWindowURL } from "./window-state"
const sampleInterval = 1000 const sampleInterval = 1000
const samplePeriod = 15000 const samplePeriod = 15000
@@ -46,7 +47,7 @@ export function createUnresponsiveSampler(win: BrowserWindow, name: string) {
const message = [ const message = [
"renderer unresponsive samples", "renderer unresponsive samples",
`Window: ${name}`, `Window: ${name}`,
`URL: ${win.isDestroyed() ? "<destroyed>" : win.webContents.getURL()}`, `URL: ${safeWindowURL(win)}`,
...entries.map((entry) => `<${entry[1]}> ${entry[0]}`), ...entries.map((entry) => `<${entry[1]}> ${entry[0]}`),
`Total Samples: ${total}`, `Total Samples: ${total}`,
].join("\n") ].join("\n")
+29
View File
@@ -0,0 +1,29 @@
export const destroyedWindowURL = "<destroyed>"
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
}
}
+5 -9
View File
@@ -13,6 +13,7 @@ import { getStore, removeStoreFile } from "./store"
import { PINCH_ZOOM_ENABLED_KEY, WINDOW_IDS_KEY } from "./store-keys" import { PINCH_ZOOM_ENABLED_KEY, WINDOW_IDS_KEY } from "./store-keys"
import { createUnresponsiveSampler } from "./unresponsive" import { createUnresponsiveSampler } from "./unresponsive"
import { createWindowRegistry } from "./window-registry" import { createWindowRegistry } from "./window-registry"
import { safeWindowURL } from "./window-state"
const root = dirname(fileURLToPath(import.meta.url)) const root = dirname(fileURLToPath(import.meta.url))
const rendererRoot = join(root, "../renderer") const rendererRoot = join(root, "../renderer")
@@ -364,7 +365,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
errorCode, errorCode,
errorDescription, errorDescription,
validatedURL, validatedURL,
currentURL: win.webContents.getURL(), currentURL: safeWindowURL(win),
isMainFrame, isMainFrame,
}, },
"error", "error",
@@ -386,12 +387,7 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
}) })
win.webContents.on("render-process-gone", (_event, details) => { win.webContents.on("render-process-gone", (_event, details) => {
sampler.stopAndFlush() sampler.stopAndFlush()
writeLog( writeLog("window", "renderer process gone", { window: name, currentURL: safeWindowURL(win), details }, "error")
"window",
"renderer process gone",
{ window: name, currentURL: win.webContents.getURL(), details },
"error",
)
void show( void show(
"OpenCode window terminated unexpectedly", "OpenCode window terminated unexpectedly",
[`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? "<unknown>"}`].join("\n"), [`Window: ${name}`, `Reason: ${details.reason}`, `Code: ${details.exitCode ?? "<unknown>"}`].join("\n"),
@@ -399,12 +395,12 @@ function wireWindowRecovery(win: BrowserWindow, name: string) {
) )
}) })
win.on("unresponsive", () => { 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() sampler.start()
void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true) void show("OpenCode is not responding", "You can relaunch the app, open the logs, or keep waiting.", true)
}) })
win.on("responsive", () => { 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() sampler.stopAndFlush()
}) })
win.webContents.on("console-message", (_event, level, message, line, sourceId) => { win.webContents.on("console-message", (_event, level, message, line, sourceId) => {