responsively-org/responsively-app · error · Error

"mainWindow" is not defined

Error message

"mainWindow" is not defined

What it means

This is an internal defensive assertion thrown inside the 'ready-to-show' event handler in createWindow. After the async initInstance() call completes, the module-level mainWindow variable is checked; if it became null/undefined (e.g. the window was closed during initialization), the code throws to prevent calling methods on a dead BrowserWindow reference. It signals a race between app initialization and window lifetime rather than a user-facing failure.

Source

Thrown at desktop-app/src/main/main.ts:203

      needsFocusFix = false;
      triggeringProgrammaticBlur = true;
      setTimeout(function () {
        mainWindow!.blur();
        mainWindow!.focus();
        setTimeout(function () {
          triggeringProgrammaticBlur = false;
        }, 100);
      }, 100);
    }
  });

  mainWindow.on('ready-to-show', async () => {
    if (!isAppInitiated) {
      await initInstance();
      setIsAppInitiated();

      if (!mainWindow) {
        throw new Error('"mainWindow" is not defined');
      }
      webPermissionHandlers.init();
      if (process.env.START_MINIMIZED) {
        mainWindow.minimize();
      } else if (process.env.E2E_TEST === 'true' && process.env.E2E_HEADLESS === 'true') {
        windowShownOnOpen = true;
      } else if (process.env.E2E_TEST === 'true') {
        mainWindow.showInactive();
        windowShownOnOpen = true;
      } else {
        mainWindow.showInactive();
        if (!windowShownOnOpen) {
          windowShownOnOpen = true;
          mainWindow.show();
        } else {
          mainWindow.showInactive();
        }
      }

View on GitHub (pinned to e5623c5a70)

Solutions

  1. Guard the handler before awaiting: if (!mainWindow) return; at the top of the ready-to-show callback and after the await
  2. Make the throw a graceful early return instead, since throwing inside an async Electron event handler only produces an unhandled rejection
  3. Track window lifetime with a flag or use mainWindow.isDestroyed() rather than relying on the module-level variable
  4. In tests, ensure the window stays open until ready-to-show completes

Example fix

// before
await initInstance();
setIsAppInitiated();
if (!mainWindow) {
  throw new Error('"mainWindow" is not defined');
}
// after
if (!mainWindow || mainWindow.isDestroyed()) return;
await initInstance();
if (!mainWindow || mainWindow.isDestroyed()) return;
setIsAppInitiated();
Defensive patterns

Strategy: type-guard

Validate before calling

const win = mainWindow;
if (!win || win.isDestroyed()) return; // run before AND after the await

Type guard

function isLiveWindow(w: Electron.BrowserWindow | null): w is Electron.BrowserWindow {
  return w !== null && !w.isDestroyed();
}

Try / catch

mainWindow.on('ready-to-show', async () => {
  try {
    await initInstance();
  } catch (err) {
    console.error('initInstance failed', err);
    return;
  }
  if (!isLiveWindow(mainWindow)) return; // window closed during init
  // ... proceed
});

Prevention

When it happens

Trigger: The user (or the OS/test harness) closes the window while initInstance() is still awaiting during the 'ready-to-show' event, so the 'closed' handler sets mainWindow = null before the null check runs.

Common situations: Fast app quit on startup (user closes splash window immediately), E2E test harnesses tearing down the window mid-initialization, START_MINIMIZED flows combined with early quit, or any refactor that makes initInstance() async/slow.

Related errors


AI-assisted analysis of responsively-org/responsively-app@e5623c5a70 (2026-08-31). Data as JSON: /api/errors/4b9e1a29d05bda6e. Report an issue: GitHub.