From 9872e9989f5826e47fa8c8ab2782b5cac1f8a8fa Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 01:05:13 -0700 Subject: [PATCH] Log packaged single-instance lock exits (#3472) --- src/main/index.ts | 16 +++++++--------- src/main/startup/single-instance-lock.test.ts | 17 ++++++++++++++++- src/main/startup/single-instance-lock.ts | 7 +++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 8ba2c81c749..d1d9c5ff7ad 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -55,7 +55,10 @@ import { import { startFirstWindowStartupServices } from './startup/first-window-startup-services' import { getDevInstanceIdentity } from './startup/dev-instance-identity' import { hydrateShellPath, mergePathSegments } from './startup/hydrate-shell-path' -import { acquireSingleInstanceLock } from './startup/single-instance-lock' +import { + acquireSingleInstanceLock, + logSingleInstanceLockFailure +} from './startup/single-instance-lock' import { RateLimitService } from './rate-limits/service' import { getInitialClaudeRateLimitTarget } from './rate-limits/claude-rate-limit-target' import { getInitialCodexRateLimitTarget } from './rate-limits/codex-rate-limit-target' @@ -273,14 +276,9 @@ function getExpectedTeardownScope(webContentsId?: number): ExpectedTeardownScope const hasSingleInstanceLock = is.dev && !isServeMode ? true : acquireSingleInstanceLock(app, focusExistingWindow) if (!hasSingleInstanceLock) { - if (is.dev) { - // Why: packaged runs have no attached console, but dev runs do. Emit a - // single line so a `pnpm dev` operator does not mistake a silent exit - // for a broken launcher. - console.log( - '[single-instance] Another Orca instance is already running against this userData path — focusing existing window.' - ) - } + // Why: if Electron returns a false negative here, packaged macOS launches + // otherwise look like silent crashes. `open --stderr` can capture this line. + logSingleInstanceLockFailure() app.quit() } diff --git a/src/main/startup/single-instance-lock.test.ts b/src/main/startup/single-instance-lock.test.ts index 4b1fe7a1c12..5b917f6ac4d 100644 --- a/src/main/startup/single-instance-lock.test.ts +++ b/src/main/startup/single-instance-lock.test.ts @@ -1,6 +1,10 @@ import type { App } from 'electron' import { describe, expect, it, vi } from 'vitest' -import { acquireSingleInstanceLock } from './single-instance-lock' +import { + acquireSingleInstanceLock, + logSingleInstanceLockFailure, + SINGLE_INSTANCE_LOCK_FAILURE_MESSAGE +} from './single-instance-lock' type Listener = (...args: unknown[]) => void @@ -65,3 +69,14 @@ describe('acquireSingleInstanceLock', () => { expect(onSecondInstance).toHaveBeenCalledTimes(1) }) }) + +describe('logSingleInstanceLockFailure', () => { + it('emits a production-visible diagnostic for the early quit path', () => { + const logger = { error: vi.fn() } + + logSingleInstanceLockFailure(logger) + + expect(logger.error).toHaveBeenCalledWith(SINGLE_INSTANCE_LOCK_FAILURE_MESSAGE) + expect(logger.error.mock.calls[0]?.[0]).toContain('Electron/macOS single-instance lock failure') + }) +}) diff --git a/src/main/startup/single-instance-lock.ts b/src/main/startup/single-instance-lock.ts index 0227d454cc0..67a32c54ae5 100644 --- a/src/main/startup/single-instance-lock.ts +++ b/src/main/startup/single-instance-lock.ts @@ -1,5 +1,8 @@ import type { App } from 'electron' +export const SINGLE_INSTANCE_LOCK_FAILURE_MESSAGE = + '[single-instance] Another Orca instance is already running for this userData profile; exiting this launch after requesting the existing window. If no Orca process is running, this may be an Electron/macOS single-instance lock failure.' + /** * Why: Orca writes two canonical discovery files into `/`: * `orca-runtime.json` (RPC endpoint + authToken for the bundled CLI) and @@ -25,3 +28,7 @@ export function acquireSingleInstanceLock(app: App, onSecondInstance: () => void app.on('second-instance', onSecondInstance) return true } + +export function logSingleInstanceLockFailure(logger: Pick = console): void { + logger.error(SINGLE_INSTANCE_LOCK_FAILURE_MESSAGE) +}