Skip to content

feat(desktop): graceful main-process startup-crash UI (NAN-701)#428

Draft
yagudaev wants to merge 2 commits into
mainfrom
michael/nan-701-desktop-startup-crash-ui
Draft

feat(desktop): graceful main-process startup-crash UI (NAN-701)#428
yagudaev wants to merge 2 commits into
mainfrom
michael/nan-701-desktop-startup-crash-ui

Conversation

@yagudaev

@yagudaev yagudaev commented May 10, 2026

Copy link
Copy Markdown
Owner

Why

When the Electron main process throws an uncaught exception during startup, the user gets the platform-default dialog: yellow-triangle modal, a raw stack trace dumping app.asar paths, and an OK button that just dismisses the box and leaves the app dead. No log to attach, no telemetry, no recovery path. We saw this in v0.7.0–v0.10.0 with the archiver crash; this ticket replaces the default dialog so any future startup crash gives the user — and us — somewhere to go.

Closes NAN-701.

What changed

  • New desktop/src/main/startup-crash.ts — wires process.on('uncaughtException') and process.on('unhandledRejection') as the first thing the main process does, before any service spawns.
  • Crash log file — full unsanitized exception (with stack, electron / node versions, source) written to ~/Library/Logs/VoiceClaw/startup-crash-<ISO>.log synchronously, so even if the dialog can't render the support trail still exists.
  • Sanitized telemetry — fires app.startup_failed via the existing PostHog wrapper with error_class, error_message, stack_preview, app_version, platform, arch, source. Sanitization strips the user's home dir (/Users/<name>/…~/…) and any <App>.app/Contents prefix, and clamps the formatted summary at 500 chars.
  • Custom dialog — short non-technical message, log path, and four buttons: Reveal Logs, Copy Diagnostic, Reinstall, Quit. The sanitized <class>: <message> plus the top three stack frames go into the dialog's detail field (smaller text under the main message — no separate expand UI; native dialog doesn't support one). Quit is the default + cancel, so Esc / Enter both close cleanly.
  • Copy Diagnostic — copies <log-path>\n\n<sanitized summary> to the clipboard and shows a follow-up confirmation dialog.
  • Idempotent — a single crashHandled flag means multiple exceptions in sequence still produce one dialog.
  • VOICECLAW_FORCE_CRASH=1 yarn dev — dev-only escape hatch that throws once whenReady resolves, so the dialog can be exercised end-to-end. No effect in packaged builds.
  • Docs — adds a Startup crash dialog section to docs/src/content/docs/desktop/troubleshooting.mdx describing each button.

Screenshot

Triggered with VOICECLAW_FORCE_CRASH=1 yarn dev:

NAN-701 startup-crash dialog

Note the path is sanitized to ~/Library/Logs/... and ~/code/voiceclaw/... rather than /Users/<name>/....

Test evidence

End-to-end exercised with VOICECLAW_FORCE_CRASH=1 yarn dev:

  • ✅ Custom dialog rendered (NOT the raw stack-trace one)

  • ✅ Path in the dialog reads ~/Library/Logs/VoiceClaw/startup-crash-2026-05-10T18-48-08-577Z.log (sanitized)

  • ✅ Crash log file appeared on disk with full unsanitized stack:

    timestamp: 2026-05-10T18:48:08.577Z
    source: unhandledRejection
    app_version: 0.10.51
    platform: darwin
    arch: arm64
    electron: 41.3.0
    node: 24.15.0
    
    Error: VOICECLAW_FORCE_CRASH triggered
        at /Users/michaelyagudaev/code/voiceclaw/voiceclaw-nan-701/desktop/out/main/index.js:3631:8
    
  • ✅ Buttons: Reveal Logs · Copy Diagnostic · Reinstall · Quit (Quit default)

  • ✅ Sanitization confirmed in dialog text + clipboard payload (no /Users/<actual-name>/…, no /Applications/<App>.app/Contents/…)

Unit tests

desktop/src/main/startup-crash.test.ts — 12 tests, all passing alongside the existing 127:

  • sanitizeStartupError strips ~, <app>, caps at 5 frames, truncates at 500 chars, preserves error class
  • buildClipboardPayload puts the log path on line 1 and the sanitized summary below
  • handleStartupCrash writes log, fires telemetry, shows dialog, exits 1
  • handleStartupCrash is idempotent across repeated calls
  • Reveal Logs / Copy Diagnostic / Reinstall button paths each invoke their respective shell / clipboard handlers
  • Non-Error throws (e.g. a plain string rejection) are coerced before sanitizing
Test Files  14 passed (14)
     Tests  139 passed (139)

Test plan

  • yarn typecheck — clean
  • yarn test — 139/139 pass
  • VOICECLAW_FORCE_CRASH=1 yarn dev shows the new dialog
  • Crash log file appears at the documented path with full stack
  • Sanitization confirmed in dialog text + tests
  • Idempotent guard verified by test (3 sequential calls → 1 dialog)
  • Reviewer: spot-check the dialog renders on a clean build
  • Reviewer: confirm app.startup_failed event lands in PostHog after a real test crash

Out of scope (per ticket)

  • Auto-recovery from corrupted state — separate ticket if needed.
  • Renderer-process error handling — handled by React error boundaries.
  • Replacing the native macOS Crash Reporter for true segfaults — JS-level only.
  • Sending crash reports automatically — telemetry + clipboard copy give the user agency.

🤖 Generated with Claude Code

Replace Electron's default uncaught-exception dialog (raw stack-trace
modal that gives the user nowhere to go) with a custom dialog that:

- Writes the full unsanitized exception to ~/Library/Logs/VoiceClaw/
  startup-crash-<ISO>.log so support has the real trace.
- Fires a sanitized PostHog `app.startup_failed` event so we get a
  remote signal even when the user can't read their own logs.
- Shows four actionable buttons: Reveal Logs, Copy Diagnostic,
  Reinstall (opens the latest release), and Quit.
- Strips the user's home dir and the `<App>.app/Contents` prefix from
  anything the user might paste publicly (clipboard + dialog text).
- Is idempotent — multiple uncaughtException / unhandledRejection
  events in sequence still surface only one dialog.

A `VOICECLAW_FORCE_CRASH=1 yarn dev` escape hatch throws inside
whenReady so the dialog can be exercised end-to-end without breaking
real code paths. Adds a Troubleshooting docs section explaining what
each button does.

Closes NAN-701
@vercel

vercel Bot commented May 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
voiceclaw Ready Ready Preview, Comment May 10, 2026 7:00pm

Request Review

PR-only artifact so reviewers can see the dialog rendering without
running yarn dev with VOICECLAW_FORCE_CRASH=1 themselves.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant