Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 40 additions & 15 deletions app/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,12 @@
compress: true,
});

// Enforce a single running instance. Launching a second copy would race on
// the sessions SQLite db, the .vite dev cache, and the user-data dir — and
// most commonly just confuses the user. When the second instance tries to
// start, surface the existing window instead.
// Enforce a single running instance. The lock loser exits, while the primary
// process handles `second-instance` by focusing or recreating its main window.
if (!app.requestSingleInstanceLock()) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 6, 2026

Choose a reason for hiding this comment

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

P1: This change removes newer-instance handoff and always keeps the currently running instance, so manual installs/updates cannot take over when an older process is already open.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/index.ts, line 46:

<comment>This change removes newer-instance handoff and always keeps the currently running instance, so manual installs/updates cannot take over when an older process is already open.</comment>

<file context>
@@ -48,22 +41,9 @@ crashReporter.start({
-if (!app.requestSingleInstanceLock(singleInstanceLaunchData)) {
+// Enforce a single running instance. The lock loser exits, while the primary
+// process handles `second-instance` by focusing or recreating its main window.
+if (!app.requestSingleInstanceLock()) {
   app.exit(0);
 }
</file context>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix with Cubic

app.quit();
throw new Error('another instance is already running');
app.exit(0);
}
app.on('second-instance', () => {
const windows = BrowserWindow.getAllWindows();
const main = windows.find((w) => !w.isDestroyed() && !w.isMinimized()) ?? windows[0];
if (main) {
if (main.isMinimized()) main.restore();
main.show();
main.focus();
}
});
app.on('second-instance', handleSecondInstanceLaunch);

// Populate the native About dialog (macOS + Linux) instead of showing the
// default Electron panel with no branding.
Expand All @@ -72,13 +61,13 @@
import { createShellWindow } from './window';
import { createTray, refreshTrayMenu } from './tray';
// Track B — Pill + hotkeys
import { createPillWindow, togglePill, showPill, hidePill, sendToPill, setPillHeight, PILL_HEIGHT_COLLAPSED, PILL_HEIGHT_EXPANDED } from './pill';

Check warning on line 64 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'./pill' imported multiple times

Check warning on line 64 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'showPill' is defined but never used. Allowed unused vars must match /^_/u
import { createLogsWindow, attachToHub as attachLogsToHub, toggleLogs, hideLogs, getLogsWindow, showLogs, setLogsMode, updateLogsAnchor, focusLogsFollowUp } from './logsPill';
import * as takeoverOverlay from './takeoverOverlay';
import { sendSessionNotification } from './notifications';
import { registerHotkeys, unregisterHotkeys, getGlobalCmdbarAccelerator, setGlobalCmdbarAccelerator } from './hotkeys';
import { makeRequest, PROTOCOL_VERSION } from '../shared/types';

Check warning on line 69 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'PROTOCOL_VERSION' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 69 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'makeRequest' is defined but never used. Allowed unused vars must match /^_/u
import type { AgentEvent } from '../shared/types';

Check warning on line 70 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'AgentEvent' is defined but never used. Allowed unused vars must match /^_/u
// Identity
import { AccountStore } from './identity/AccountStore';
import { createOnboardingWindow } from './identity/onboardingWindow';
Expand All @@ -103,7 +92,7 @@
import { bootstrapHarness, harnessDir } from './hl/harness';
import { runEngine, DEFAULT_ENGINE_ID } from './hl/engines';
import { getEngine, setEngine, type EngineId } from './hl/engine';
import { forwardAgentEvent } from './pill';

Check warning on line 95 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'./pill' imported multiple times

Check warning on line 95 in app/src/main/index.ts

View workflow job for this annotation

GitHub Actions / Lint (TS)

'forwardAgentEvent' is defined but never used. Allowed unused vars must match /^_/u
// Session management
import { SessionManager } from './sessions/SessionManager';
import { BrowserPool } from './sessions/BrowserPool';
Expand Down Expand Up @@ -236,6 +225,42 @@
return 'about:blank';
}

// ---------------------------------------------------------------------------
// Single-instance focus
// ---------------------------------------------------------------------------
function handleSecondInstanceLaunch(): void {
mainLogger.info('main.singleInstance.focusExisting', {
currentVersion: app.getVersion(),
});
showAndFocusPrimaryWindow();
}

function showAndFocusPrimaryWindow(): void {
const windows = BrowserWindow.getAllWindows().filter((win) => !win.isDestroyed());
const preferred = [shellWindow, onboardingWindow, BrowserWindow.getFocusedWindow(), ...windows]
.find((win): win is BrowserWindow => Boolean(win && !win.isDestroyed()));

if (preferred) {
if (preferred.isMinimized()) preferred.restore();
preferred.show();
preferred.focus();
return;
}

setTimeout(() => {
if (BrowserWindow.getAllWindows().some((win) => !win.isDestroyed())) return;
if (accountStore.isOnboardingComplete()) {
openShellAndWire();
return;
}
onboardingWindow = createOnboardingWindow();
onboardingWindow.on('closed', () => {
mainLogger.info('main.onboardingWindow.closed');
onboardingWindow = null;
});
}, 100);
}

// ---------------------------------------------------------------------------
// Shell window factory
// ---------------------------------------------------------------------------
Expand Down
Loading