Skip to content

fix(diagnostics): make startup crashes self-reporting; cross-env for dev script - #41

Merged
julia-kafarska merged 1 commit into
mainfrom
fix/crash-logging-and-cross-env
Aug 4, 2026
Merged

fix(diagnostics): make startup crashes self-reporting; cross-env for dev script#41
julia-kafarska merged 1 commit into
mainfrom
fix/crash-logging-and-cross-env

Conversation

@julia-kafarska

Copy link
Copy Markdown
Member

Follow-up to #39. That PR fixed the crash; this one makes the next one diagnosable.

Why

Three users independently root-caused #36/#37/#38 from OS crash reports, because the app recorded nothing itself. onnxruntime aborted natively inside InferenceSession.create — no JS error, no catch block, no chance to flush a buffered stream — and crashReporter was never started. %APPDATA%\Out Loud didn't exist afterwards, and the Windows event log was empty. From the user's side the app simply vanished ~3s after launch.

What

New electron/crash-log.ts: an append-only breadcrumb log shared by the main process and the TTS worker thread.

Every write is synchronous. That's the whole design constraint — the line has to be on disk before the next statement runs, so it survives an abort that never unwinds. Verified against process.abort() (SIGABRT, no flush): the breadcrumb persists.

console.log/warn/error are teed into it, so the breadcrumbs the app already emits (ONNX session providers: cpu, Model preloaded successfully) get captured in packaged builds, where stdout goes nowhere a user can reach.

The worker writes to the file directly rather than through its piped stdout. A native abort kills the thread with whatever is still sitting in the pipe — and the lost line is precisely the one identifying where it died. The main process passes the path via workerData; driving the worker standalone (as the website's sample generator does) just skips logging.

Also:

  • crashReporter.start({ uploadToServer: false }) at module load, so native minidumps land in crashDumps. Local only — nothing is uploaded anywhere. Confirmed the Crashpad database is created on launch.
  • The ONNX session parameters are now logged before the call that can abort, so even a truncated log names the providers, optimisation level and arch.
  • child-process-gone, render-process-gone, uncaughtException, unhandledRejection handlers.
  • Rotates at 2 MB keeping one previous file. All logging is best-effort and wrapped — diagnostics can never be what breaks startup.

Had this existed, #36 would have been a one-line paste:

=== session 2026-08-04T08:04:47.197Z version=2.1.1 electron=41.2.2 platform=darwin arch=arm64 packaged=false
...
[worker] Loaded embedded model: .../model_q8f16.onnx
[worker] [TTS Worker] Creating ONNX session: providers=[cpu] graphOptimizationLevel=all arch=arm64   <-- log ends here

Log location is printed at startup and is app.getPath("logs")/out-loud.log%APPDATA%\Out Loud\logs\ on Windows, ~/Library/Logs/Out Loud/ on macOS.

Unrelated fix

electron:dev used Unix-only NODE_ENV=development electron ., which fails on Windows — also reported in #36. Routed through cross-env (added as a dev dependency).

Verification

  • npm run check (lint, format, knip, typecheck) passes.
  • Ran the app: single session header, main and worker breadcrumbs interleaved with timestamps, Crashpad database created.
  • process.abort() survival test as described above.

Note: knip.config.js needed electron/crash-log.ts added to the entry list — the same treatment the other electron modules already get, since imports resolve to the committed .js and the .ts source would otherwise read as unused. The config already documents this.

🤖 Generated with Claude Code

…cript

Three users independently root-caused the same startup crash (#36/#37/#38)
from OS crash reports, because the app itself recorded nothing: onnxruntime
aborted natively inside InferenceSession.create, which gives JS no chance to
run a catch block or flush a buffered stream, and crashpad was never started.
%APPDATA%\Out Loud didn't even exist afterwards.

Add electron/crash-log.ts, an append-only breadcrumb log shared by the main
process and the TTS worker. Every write is synchronous, so the last line is
on disk before the next statement runs and survives a hard abort — verified
against process.abort() (SIGABRT, no unwinding), where the breadcrumb
persists. console.log/warn/error are teed into it, so the breadcrumbs the app
already emits are captured in packaged builds where stdout goes nowhere.

The worker writes to the file directly rather than via its piped stdout: a
native abort kills the thread with whatever is still in the pipe, and the
lost line is exactly the one identifying where it died. The main process
passes the path through workerData; standalone worker use skips logging.

Also:
- crashReporter.start({uploadToServer: false}) at module load, so native
  minidumps land locally in crashDumps. Nothing is uploaded anywhere.
- Log the ONNX session parameters BEFORE the call that can abort, so a
  truncated log still names the providers, optimisation level and arch.
- child-process-gone / render-process-gone / uncaughtException /
  unhandledRejection handlers.
- Rotate at 2 MB keeping one previous file; all logging is best-effort and
  wrapped so diagnostics can never break startup.

Separately, electron:dev used Unix-only `NODE_ENV=development electron .`,
which fails on Windows (also reported in #36). Route it through cross-env.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@julia-kafarska
julia-kafarska merged commit f9d54ee into main Aug 4, 2026
1 check passed
@julia-kafarska
julia-kafarska deleted the fix/crash-logging-and-cross-env branch August 4, 2026 08:14
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