Skip to content

fix(desktop): surface compiled-app startup errors instead of exiting silently#35567

Merged
crowlKats merged 6 commits into
mainfrom
orch/divybot-611
Jul 7, 2026
Merged

fix(desktop): surface compiled-app startup errors instead of exiting silently#35567
crowlKats merged 6 commits into
mainfrom
orch/divybot-611

Conversation

@divybot

@divybot divybot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

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/await rejection), LibMainWorker::run() returns Err, which propagates to the laufey::main! handler in cli/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 / unhandledrejection listeners (installed by desktop_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 a log::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: add DesktopStartupError (carries a pre-formatted, user-facing message) and a new else if has_desktop run branch that mirrors LibMainWorker::run() but tags the preload/main-module/load-event phase errors. Plain deno compile (else) is unchanged.
  • cli/rt_desktop/lib.rs: the handler downcasts to DesktopStartupError and uses should_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).
  • Unit tests in both crates.

Note on "workspace"

The issue is framed as a workspace problem, so I checked that first: I built deno + libdenort locally and confirmed that generic cross-workspace member imports resolve correctly in a compiled desktop binary (a minimal 2-member workspace where pkg-a imports @test/b by name compiles, resolves the member, opens the window, and exits cleanly). The serialized workspace resolver is shared between deno compile and deno 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:

  • happy path still runs (window created, clean exit);
  • a previously-silent startup error (failed import / link error) now surfaces — the process blocks on the native "Application Error" dialog instead of vanishing.

cargo test -p denort -p denort_desktop green; cargo clippy clean; cargo fmt.

Refs #35544

Closes denoland/divybot#611

…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]>
@divybot
divybot marked this pull request as ready for review June 27, 2026 13:18
divybot and others added 2 commits June 28, 2026 08:35
@bartlomieju

Copy link
Copy Markdown
Member

Thanks, the root-cause analysis is right and the fix is well scoped. Two things to address before merging:

  1. Non-ASCII em-dashes in comments. The new comments in both files use repeatedly (the DesktopStartupError doc comment, the branch comment in run.rs, the should_show_native_error_dialog doc, and the test module comments). Please keep source comments ASCII-only; replace them with commas, colons, or sentence breaks.

  2. DesktopStartupError drops the source error. AnyError::new(DesktopStartupError { message }) discards the original error chain, so log::error!("[desktop] Deno runtime error: {:?}", err) in run_desktop now only prints the pre-formatted message with no cause chain. Please carry the original error (e.g. a source: AnyError field) and implement Error::source() so debug logs keep the chain.

Non-blocking, take or leave:

  • run.rs now has two hand-copies of LibMainWorker::run() (the HMR branch and this one), so a new dispatch step added to run() would silently drift. Splitting run() into a load phase and a loop phase so the desktop branch only tags the first call would remove the duplication; fine as a follow-up.
  • The deno desktop workspace issue #35544 backstory (window blinks open, no visible stderr) is re-explained in four places. One canonical explanation on DesktopStartupError with short pointers elsewhere would read better.

crowlKats and others added 2 commits July 7, 2026 12:39
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]>

@crowlKats crowlKats left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@crowlKats
crowlKats merged commit dc6a95f into main Jul 7, 2026
136 checks passed
@crowlKats
crowlKats deleted the orch/divybot-611 branch July 7, 2026 17:12
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.

3 participants