Terminate duplicate Finicky instances at launch (fixes #515) - #516
Conversation
macOS Launch Services normally routes GetURL events to an already-running Finicky instance, but that routing can fail (stale LS registration after app moves/updates, different bundle paths, SSO agents launching from another context) and silently spawn a second process that proceeds to create its own status bar icon. Without a guard, duplicates accumulate in the menu bar over time. Enumerate running applications with our bundle identifier at launch and terminate any other instances so we're the single surviving process. Verified locally: launching the patched build with 16 stale instances already running reduced the count to 1 (the new process), with NSLog entries confirming each termination. Fixes johnste#515
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdded a runtime single-instance guard: on launch the app resolves its bundle ID, enumerates other running instances (excluding itself), logs and requests graceful termination, waits briefly while spinning the run loop, then force-terminates any lingering duplicates before continuing startup. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/finicky/src/main.m`:
- Around line 69-72: Replace the current immediate-check logic around [app
terminate] so that after sending the terminate request (the NSLog and [app
terminate] call in the duplicate-instance block) you poll the
NSRunningApplication instance (app) for isTerminated with a short, bounded wait
(e.g., loop with small sleep and overall timeout of a few seconds); if the
process is still alive after the timeout call [app forceTerminate]. Keep the
original NSLog for pid ([app processIdentifier]) and ensure you only skip
forceTerminate when isTerminated becomes true within the timeout window.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
[NSRunningApplication terminate] returning YES only confirms the request was dispatched; the target may still be running when the call returns. If a duplicate hung or ignored SIGTERM we'd reach createStatusItem alongside it, defeating the single-instance guarantee. Fire terminate() on all duplicates in parallel, then share a single 1s deadline across the poll (so N hung duplicates don't each cost 1s), then forceTerminate any holdouts. Addresses review feedback on johnste#516.
|
Good call from CodeRabbit — Re-verified: spawned 3 stock Finicky instances with |
Summary
Fixes the menu-bar-pile-up described in #515: Finicky has no single-instance guard, so whenever macOS spawns a second process (stale Launch Services registration after app moves/updates, different bundle paths, SSO/enterprise-auth agents launching from another context) a new status bar item accumulates and never gets reaped.
Adds a simple guard at the top of
applicationDidFinishLaunching:inapps/finicky/src/main.m: enumerate[NSRunningApplication runningApplicationsWithBundleIdentifier:]and call[app terminate](falling back toforceTerminate) on any instance that isn't us. Normal operation is unaffected — there's nothing to terminate when no duplicates exist.Why this approach
LSMultipleInstancesProhibitedinInfo.plistand no runtime check previously existed; the app relied entirely on Launch Services routing GetURL events to the existing instance, which fails in the scenarios above.Test plan
Verified locally:
Finicky-test.app(viaBUILD_TARGET_ARCH=test ./scripts/build.sh).pgrep -fl Finickyshowed 16 running instances on my machine (the exact scenario from Multiple Finicky instances accumulate in menu bar (no single-instance guard) #515).open apps/finicky/build/Finicky-test.app, only 1 process remains — the new one.log show --predicate 'processImagePath CONTAINS "Finicky-test"'shows 16Terminating duplicate Finicky instance (pid N)lines, one per stale instance.Notes
NSRunningApplicationenumeration — negligible.[[NSBundle mainBundle] bundleIdentifier]at runtime, so it stays correct if the bundle ID is ever renamed.Summary by CodeRabbit