denoland/deno · critical

desktop mode enabled

Error message

desktop mode enabled

What it means

BrowserWindow is Deno's desktop API class, implemented in runtime/ops/desktop.rs. Its constructor fetches Arc<dyn DesktopApi> from OpState with expect("desktop mode enabled"); that state is only installed by the desktop runtime (cli/rt_desktop, used by `deno desktop` / `deno compile --desktop`). Constructing BrowserWindow under plain `deno run` panics in native code and aborts the process — the panic cannot be caught with JS try/catch.

Source

Thrown at runtime/ops/desktop.rs:760

  }
}

struct EventTargetSetup {
  brand: v8::Global<v8::Value>,
  set_event_target_data: v8::Global<v8::Value>,
}

#[op2]
impl BrowserWindow {
  #[constructor]
  fn new(
    state: &OpState,
    scope: &mut v8::PinScope<'_, '_>,
    #[scoped] options: Option<BrowserWindowOptions>,
  ) -> v8::Global<v8::Value> {
    let api = state
      .try_borrow::<Arc<dyn DesktopApi>>()
      .expect("desktop mode enabled")
      .clone();

    // Use the initial window if this is the first BrowserWindow,
    // otherwise create a new one.
    let window_id = state
      .try_borrow::<InitialWindowId>()
      .and_then(|iw| iw.0.lock().unwrap().take())
      .unwrap_or_else(|| {
        let width = options.as_ref().and_then(|o| o.width).unwrap_or(800);
        let height = options.as_ref().and_then(|o| o.height).unwrap_or(600);
        let frameless =
          options.as_ref().and_then(|o| o.frameless).unwrap_or(false);
        let no_activate = options
          .as_ref()
          .and_then(|o| o.no_activate)
          .unwrap_or(false);
        let transparent_titlebar = options
          .as_ref()

View on GitHub (pinned to f7822238ca)

Solutions

  1. Run the app through the desktop runtime: `deno desktop <entry>` during development, or build with `deno compile --desktop` and run the produced binary.
  2. Move `new BrowserWindow(...)` behind an explicit entry point used only by the desktop app, not into shared/imported modules with side effects.
  3. Gate desktop construction with a runtime flag you control (e.g. a global set only in the desktop entry) so tests and CLI builds never reach the constructor.
  4. For unit tests, mock/inject the window factory instead of constructing the real class.

Example fix

// before (runs under `deno run` -> panic: desktop mode enabled)
export const win = new BrowserWindow({ width: 800, height: 600 });

// after: construct only when launched by the desktop runtime
if (globalThis.__DESKTOP_ENTRY__) {
  const win = new BrowserWindow({ width: 800, height: 600 });
}
Defensive patterns

Strategy: validation

Validate before calling

// set this only in the desktop entry, before importing shared modules
globalThis.__DESKTOP_ENTRY__ = true;

// shared module
if (globalThis.__DESKTOP_ENTRY__) {
  const win = new BrowserWindow({ width: 800, height: 600 });
}

Type guard

const isDesktopRuntime = () => globalThis.__DESKTOP_ENTRY__ === true;

Prevention

When it happens

Trigger: `new BrowserWindow({ width: 800, height: 600 })` executed in a normal `deno run main.ts` process (or any non-desktop runtime like `deno eval`, `deno test`, `deno serve`), typically because shared code imported by a desktop app was also executed outside it.

Common situations: Importing a module that creates the window at top level into a test or a server build; running the desktop entry with `deno run` during development instead of `deno run` under the desktop runtime; unit tests importing component modules that instantiate windows; code shared between CLI and desktop targets.

Related errors


AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20). Data as JSON: /api/errors/6db2db6515254908. Report an issue: GitHub.