fix(desktop): surface compiled-app startup errors instead of exiting silently#35567
Conversation
…silently A `deno desktop`-compiled app that failed while loading or first evaluating its main module (a failed import, a link error, or a top-level throw/await rejection) would exit silently: the error reached only a stderr that a Finder/Dock-launched app can't see, and the native error dialog was suppressed for JS errors on the assumption that the app's own `error`/`unhandledrejection` listeners had already reported it. Those listeners only fire for events dispatched during the event loop, so a load-phase failure was observed by nobody — the window blinked open and immediately closed with no logs (deno#35544). Tag failures from the main-module load/first-eval phase as `DesktopStartupError` and always surface those (and non-JS crashes) in a native dialog; post-load JS errors are still left to the app's own listeners to avoid a duplicate dialog. Plain `deno compile` is unaffected. Co-Authored-By: Divy Srivastava <[email protected]>
Co-Authored-By: Divy Srivastava <[email protected]>
Co-Authored-By: Divy Srivastava <[email protected]>
|
Thanks, the root-cause analysis is right and the fix is well scoped. Two things to address before merging:
Non-blocking, take or leave:
|
Address review feedback on #35567: - `DesktopStartupError` now carries the original error as an `Error::source()`, so the `{:?}` debug log in `run_desktop` keeps the full cause chain instead of only the pre-formatted message. - Replace non-ASCII em-dashes in the new comments with ASCII punctuation. - Deduplicate the #35544 backstory: keep the canonical explanation on `DesktopStartupError` and point to it from the run branch comment. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Address the non-blocking review feedback on #35567: - Split `LibMainWorker::run()` into `execute_load_phase()` (preload + main module + load event) and `run_event_loop_to_completion()` (event loop + unload/exit + exit code). `run()` now composes the two, so the desktop branch tags only the load phase as `DesktopStartupError` and reuses the shared loop phase instead of hand-copying it. The HMR branch reuses `execute_load_phase()` too. A new dispatch step added to a phase now flows through to every branch instead of silently drifting. - Deduplicate the #35544 backstory: keep the canonical explanation on `DesktopStartupError` and replace the re-explanations in `should_show_native_error_dialog`'s doc and its test comment with short pointers. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Problem
A
deno desktop-compiled app that fails while loading or first evaluating its main module exits silently — the window blinks open for ~1s and closes with no error anywhere. This is the report in #35544 (and the reason the reporter, a workspace/monorepo user, couldn't tell what was wrong: "No logs anywhere — if there is a way to enable/view logs anywhere, please let me know").Root cause
When the main module fails to load/instantiate/first-evaluate (a failed import, a link error, a top-level
throw/awaitrejection),LibMainWorker::run()returnsErr, which propagates to thelaufey::main!handler incli/rt_desktop/lib.rs. That handler showed a native error dialog only for non-JS errors (if !is_js_error), on the assumption that "JS errors are already handled by the error reporting JS listener."That assumption is false for the load phase. The app's global
error/unhandledrejectionlisteners (installed bydesktop_error_reporting_js) only fire for events dispatched during the event loop. A module that fails before the loop starts is observed by nobody: the Rust side suppressed the dialog, and the only output was alog::error!to a stderr that a Finder/Dock-launched app can't see. → silent exit.(Post-load runtime errors are unaffected: those do reach the JS listener, which shows a dialog.)
Fix
cli/rt/run.rs: addDesktopStartupError(carries a pre-formatted, user-facing message) and a newelse if has_desktoprun branch that mirrorsLibMainWorker::run()but tags the preload/main-module/load-event phase errors. Plaindeno compile(else) is unchanged.cli/rt_desktop/lib.rs: the handler downcasts toDesktopStartupErrorand usesshould_show_native_error_dialog(is_startup, is_js_error)so startup failures (and non-JS crashes) always get a native dialog, while post-load JS errors are still left to the app's own listeners (no duplicate dialog).Note on "workspace"
The issue is framed as a workspace problem, so I checked that first: I built
deno+libdenortlocally and confirmed that generic cross-workspace member imports resolve correctly in a compiled desktop binary (a minimal 2-member workspace wherepkg-aimports@test/bby name compiles, resolves the member, opens the window, and exits cleanly). The serialized workspace resolver is shared betweendeno compileanddeno desktop. The real, generalizable bug behind "blinks open and closes" is the swallowed startup error — once it's surfaced, an app-specific failure (e.g. a workspace member that can't find its bundled assets at runtime) becomes diagnosable instead of invisible.Verification
Built
cargo build --bin deno+cargo build -p denort_desktop, compiled a desktop app against the local runtime (DENORT_DESKTOP_BIN), and confirmed:cargo test -p denort -p denort_desktopgreen;cargo clippyclean;cargo fmt.Refs #35544
Closes denoland/divybot#611