Skip to content

feat(main): prevent multiple app instances with single-instance lock#65

Merged
rxm96 merged 1 commit into
mainfrom
feat/single-instance-lock
Jun 14, 2026
Merged

feat(main): prevent multiple app instances with single-instance lock#65
rxm96 merged 1 commit into
mainfrom
feat/single-instance-lock

Conversation

@rxm96

@rxm96 rxm96 commented Jun 14, 2026

Copy link
Copy Markdown
Owner

What

Adds an Electron single-instance lock so only one DropPilot process can run at a time. A second launch surfaces the already-running window instead of starting a duplicate process.

Why

DropPilot defaults to close-to-tray, so the window is usually hidden-but-alive rather than closed. Without a guard, double-clicking the exe (or auto-start firing while a manual instance is already running) spins up a second process — two trackers, two PubSub connections, two trays competing over the same Twitch session.

How

In src/main/index.ts:

  • Call app.requestSingleInstanceLock() before startup. The losing instance calls app.quit() and never creates a window or starts Twitch/IPC services.
  • The winning instance handles the second-instance event by restoring the existing window from the tray (restore()/show()) and focusing it — chosen behavior: bring the running window to front.
  • A belt-and-suspenders early-return at the top of the app.whenReady() callback (if (!gotSingleInstanceLock) return;) guarantees a losing instance does no startup work even if ready still fires.

Verification

  • npm run typecheck (both tsconfigs): clean
  • npm run format:check / prettier: clean
  • npm run lint: clean for the changed file (pre-existing warnings only, in unrelated renderer/.../watch hooks)
  • npm test: 514 passed

index.ts is main-process lifecycle wiring and isn't unit-testable (consistent with the project's pure-function-only test convention), so there's no automated test for this path.

Reviewer notes

  • Not yet runtime-verified end-to-end (launching the built app twice and observing the window surface). The logic is the standard documented Electron single-instance pattern.
  • Applied unconditionally, including dev. npm run dev only ever launches one instance and vite-plugin-electron releases the lock on hot-restart; if it ever interferes, guarding with app.isPackaged is a one-line fallback.
  • Auto-update unaffected: quitAndInstall exits the old process (releasing the lock) before the new one launches.

🤖 Generated with Claude Code

Acquire app.requestSingleInstanceLock() before startup. A second launch
(double-clicking the exe while DropPilot is in the tray, or auto-start
firing alongside a manual launch) fails to acquire the lock and quits
before creating a window or starting any Twitch/IPC services. Electron
forwards that launch to the running process via the "second-instance"
event, which restores the existing window from the tray and focuses it.

A close-to-tray default means the running instance usually has a hidden
but alive window, so the handler calls show()/restore() before focus().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rxm96
rxm96 merged commit b20bab2 into main Jun 14, 2026
3 checks passed
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.

1 participant