Skip to content

fix(desktop): a dead main renderer reloads instead of leaving a blank window - #123

Merged
jayleaton merged 1 commit into
mainfrom
fix/desktop-renderer-recovery
Oct 24, 2026
Merged

jayleaton merged 1 commit into
mainfrom
fix/desktop-renderer-recovery

Conversation

@jayleaton

Copy link
Copy Markdown
Owner

The installed desktop app's main renderer died on 2026-10-23 at about 02:01 UTC and the window stayed mapped and blank for over a day, until a manual Reload. The main process and backend kept running, and about 20 backend restarts in that time never revived the window.

Summary

 main window
- render-process-gone: reload only for crashed | oom | killed | abnormal-exit
+ render-process-gone: reload for every reason except clean-exit
+ unresponsive for 30 s → forcefullyCrashRenderer() → reload
+ every 30 s, and on every backend-ready: renderer crashed or pid 0 → reload
  all triggers → one shared budget (3 reloads per 60 s), overlapping triggers reload once
  • The 10-23 trace never logged "render process gone", so the event-driven path alone can't be trusted. The liveness check asks the webContents directly: isCrashed(), or getOSProcessId() === 0 while not loading.
  • handleBackendReady used to return early whenever a main window existed (createMainIfBackendReady). It now revives a dead renderer first.
  • The recovery test deleted in test: delete 11k low-signal tests so CI runs faster and flakes less pingdotgg/t3code#22557 is back, in a broader form.

Evidence

vp test run apps/desktop/src/window/DesktopWindow.test.ts on dev-box (Linux):

  • Before (fix reverted, new tests kept): 3 failed
    • recovers killed renderers within the reload budget and skips clean exits: expected 4 to equal 5 (a launch-failed exit didn't reload)
    • revives a main window whose renderer vanished when the backend comes back: expected 1 to equal 2
    • crashes and reloads a renderer that stays unresponsive: no listener
  • After: 23/23 pass. The 5 window test files pass 51/51. vp run check:changed passes: format, lint, knip, typecheck for 2 packages, and tests.

Not verified against a live Electron renderer crash. The tests drive fake webContents through the same events and calls.

Merge Danger

Door: two-way. Code-only. Reverting it brings back the old reload rules.

Blast Radius: desktop

Only Electron main-window recovery is affected. The worst case is an extra reload of a renderer that getOSProcessId() briefly reports as 0 while it isn't loading, limited to 3 per minute. Web and mobile are unaffected.

Bottom of a 3-PR stack: this PR, then updater/evidence, then the web unpaired-load fix.

Model: Claude Opus 5.5 (claude-opus-5-5) via Claude Code in T3 Code.

🤖 Generated with Claude Code

… window

The main window only reloaded for four render-process-gone reasons, and
nothing noticed a renderer that vanished without that event or hung. On
2026-10-23 the main renderer died and the window stayed mapped and blank for
over a day; about 20 backend restarts never revived it, because
handleBackendReady skips creation whenever a window exists.

Every render-process-gone reason except clean-exit now reloads, a renderer
unresponsive for 30 s is crashed and reloaded, and a 30 s liveness check plus
every backend-ready reloads a main window whose renderer is crashed or has no
process. All triggers share the existing 3-per-minute reload budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Oct 24, 2026
@jayleaton
jayleaton marked this pull request as ready for review October 24, 2026 07:00
@jayleaton
jayleaton merged commit e0950be into main Oct 24, 2026
39 checks passed
jayleaton added a commit that referenced this pull request Oct 24, 2026
Branch pushes evicted main's pending nightly from its concurrency group, so
the nightly carrying #123/#125/#126 was cancelled twice and never shipped,
yet a cancelled run looked neutral to the health check. Cancelled runs of the
nightly release on main now count as failures; a cancelled sync still does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant