Skip to content

fix(v3): macOS SingleInstance + URL scheme — relay URL to first instance (#5089)#5170

Open
leaanthony wants to merge 5 commits intomasterfrom
fix/5089-macos-single-instance-url-scheme
Open

fix(v3): macOS SingleInstance + URL scheme — relay URL to first instance (#5089)#5170
leaanthony wants to merge 5 commits intomasterfrom
fix/5089-macos-single-instance-url-scheme

Conversation

@leaanthony
Copy link
Copy Markdown
Member

@leaanthony leaanthony commented Apr 18, 2026

Fixes #5089

The problem

When SingleInstance is active and a URL-scheme launch triggers a second process on macOS (e.g. open -n wails-single-url://hello), the URL is silently dropped. Neither OnSecondInstanceLaunch nor ApplicationLaunchedWithUrl receives it.

Why: On macOS, LaunchServices delivers URL-scheme launches via a kAEGetURL Apple Event — not via os.Args as on Windows/Linux. This event is only dispatched after [NSApp run] calls finishLaunching. The second instance was calling notifyFirstInstance()os.Exit() before any of that infrastructure ran, so the event was queued by the OS but never delivered.

The fix

Added v3/pkg/application/single_instance_darwin_url.go with captureLaunchURL():

  1. Creates [NSApplication sharedApplication] with NSApplicationActivationPolicyProhibited (no dock icon — this is a short-lived background process)
  2. Registers a kAEGetURL handler before [NSApp run]
  3. Calls [NSApp run] — this triggers finishLaunching, which signals LaunchServices the process is ready to receive Apple Events
  4. The run loop stops immediately when the URL arrives (via the event handler), or after a 300 ms safety-net timeout
  5. Returns the URL string, or "" on timeout

notifyFirstInstance() in single_instance.go calls captureLaunchURL() on darwin and appends any captured URL to SecondInstanceData.Args before sending the notification to the first instance. This matches Windows/Linux behaviour where the URL is already in os.Args.

A stub single_instance_launch_url_other.go (!darwin || ios || server) returns "" — no behaviour change on other platforms.

ApplicationLaunchedWithUrl is not fired on the second-instance relay path, consistent with Windows/Linux.

Evidence — test log (macOS 26.0, Apple Silicon)

# BEFORE fix (open -n wails-single-url://hello?n=1)
[first] OnSecondInstanceLaunch fired
[first]   Args           = [.../single-instance-url-scheme]
[first]   url-in-args?   = false  (url="")

# AFTER fix (open -n wails-single-url://hello?n=1)
[first] OnSecondInstanceLaunch fired
[first]   Args           = [.../single-instance-url-scheme wails-single-url://hello?n=1]
[first]   url-in-args?   = true  (url="wails-single-url://hello?n=1")

# AFTER fix (open -n app.app — no URL, timeout path)
[first] OnSecondInstanceLaunch fired
[first]   Args           = [.../single-instance-url-scheme]
[first]   url-in-args?   = false  (url="")

# AFTER fix (fresh first launch — ApplicationLaunchedWithUrl still works)
[first] ApplicationLaunchedWithUrl fired url="wails-single-url://hello?fresh=launch"

Second instance exit time: ~120–160 ms on URL capture; ~420 ms on timeout (300 ms + overhead).

image

Files changed

File Change
v3/pkg/application/single_instance_darwin_url.go New — darwin URL capture via Apple Events
v3/pkg/application/single_instance_launch_url_other.go New — no-op stub for other platforms
v3/pkg/application/single_instance.go Call captureLaunchURL() in notifyFirstInstance()
v3/examples/single-instance-url-scheme/README.md Updated — fixed behaviour documented
v3/release_notes.md Added fix entry

Out of scope

Adding LSMultipleInstancesProhibited to the generated Info.plist (prevents macOS 14/15 from spawning a second process entirely). Separate ticket.

Summary by CodeRabbit

  • New Features

    • Added an example app with a simple UI and tasks to build, package, run, and trigger a custom URL-scheme flow on macOS.
  • Bug Fixes

    • macOS: fixed missing URL forwarding during second-instance relays so the primary instance now receives the URL like other platforms.
  • Documentation

    • Added README and usage tasks covering setup, testing notes, macOS version differences, expected logs, and how to force relay behavior.
  • Chores

    • Added build/packaging helpers and macOS bundle metadata for the example.

Wails Documentation Agent and others added 3 commits April 18, 2026 15:39
…#5089)

Adds v3/examples/single-instance-url-scheme/ — a minimal reproduction for
wails#5089. The example enables SingleInstance and registers a custom URL
scheme (wails-single-url://) via CFBundleURLTypes. Running the app and
then triggering the scheme with a second process (e.g. `open`) exercises
the bug: on macOS the URL is delivered by LaunchServices via an Apple
Event that the exiting second process never installs a handler for,
so neither OnSecondInstanceLaunch nor ApplicationLaunchedWithUrl sees the
URL in the first instance.

This is a failing test case only — no fix is included.
…dd trigger:force task

On macOS 26.0 (tested: 25A354, Apple Silicon), LaunchServices routes URL-scheme
Apple Events directly to the running instance, so plain `open URL` does NOT spawn
a second process and the bug is not visible via that path.

Add trigger:force task (open -n) to force a new process, which reproduces the bug
on macOS 26+: second instance detects flock, relays os.Args (no URL), exits before
Apple Event handler registers — URL dropped, OnSecondInstanceLaunch fires with
url-in-args?=false.

Update README with macOS version behaviour matrix and trigger:force instructions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…heme (#5089)

On macOS, URL-scheme launches deliver the URL via NSAppleEventManager
(kAEGetURL Apple Event) rather than os.Args.  When SingleInstance is
active, the second process was calling os.Exit() before NSApplication.run
wired up the Apple Event handler, so the URL was silently dropped.

Fix: add single_instance_darwin_url.go which implements captureLaunchURL().
It briefly runs a minimal NSApplication event loop
(NSApplicationActivationPolicyProhibited, no dock icon) so LaunchServices
can deliver the kAEGetURL event.  The run loop exits immediately on URL
capture or after a 300 ms safety-net timeout.  The captured URL is appended
to SecondInstanceData.Args in notifyFirstInstance(), matching the behaviour
on Windows and Linux where the URL is already in os.Args.

ApplicationLaunchedWithUrl is not fired on the second-instance relay path,
consistent with Windows/Linux.

Tested on macOS 26 (Apple Silicon):
- open -n URL: url-in-args? = true, ~120-160 ms second-instance exit time
- open -n app (no URL): url-in-args? = false, ~420 ms (300 ms timeout)
- Fresh first-instance launch with URL: ApplicationLaunchedWithUrl fires

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 18, 2026 07:51
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60db0882-a607-4705-bcdb-680ed84f7994

📥 Commits

Reviewing files that changed from the base of the PR and between d1afeb8 and 8cbb9d6.

📒 Files selected for processing (2)
  • v3/examples/single-instance-url-scheme/Taskfile.yml
  • v3/pkg/application/single_instance_darwin_url.go
✅ Files skipped from review due to trivial changes (1)
  • v3/examples/single-instance-url-scheme/Taskfile.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/pkg/application/single_instance_darwin_url.go

Walkthrough

Adds a macOS-specific capture for URL-scheme launches in the SingleInstance flow: a short-lived NSApp run loop registers kAEGetURL, captures any launch URL (or times out), and appends it to SecondInstanceData.Args so the primary instance receives the URL when a second instance relays launch data.

Changes

Cohort / File(s) Summary
Documentation
v3/examples/single-instance-url-scheme/README.md, v3/release_notes.md
New README describing repro, macOS behavior, fix, and gotchas; minor macOS version note update in release notes.
Example Application
v3/examples/single-instance-url-scheme/main.go, v3/examples/single-instance-url-scheme/assets/index.html
New demo app and UI that logs and displays ApplicationLaunchedWithUrl and OnSecondInstanceLaunch payloads and whether a scheme URL was preserved.
Build Automation & macOS Bundle
v3/examples/single-instance-url-scheme/Taskfile.yml, v3/examples/single-instance-url-scheme/build/Taskfile.yml, v3/examples/single-instance-url-scheme/build/darwin/Taskfile.yml, v3/examples/single-instance-url-scheme/build/darwin/Info.plist, v3/examples/single-instance-url-scheme/build/darwin/Info.dev.plist
Taskfiles and plist(s) to build/package/run the example, create and codesign .app bundles, register with LaunchServices, and provide trigger/trigger:force helpers for exercising URL launch and forced new-process relay.
Core Implementation
v3/pkg/application/single_instance.go, v3/pkg/application/single_instance_darwin_url.go, v3/pkg/application/single_instance_launch_url_other.go
notifyFirstInstance now appends a captured launch URL on darwin. Added darwin-only captureLaunchURL() (cgo/Objective‑C) that registers kAEGetURL, runs a short NSApp loop with a 300ms timeout to capture the URL, and returns it; non-darwin builds return "".

Sequence Diagram(s)

sequenceDiagram
    participant LS as LaunchServices
    participant Second as Second Process
    participant Capture as captureLaunchURL (NSApp)
    participant First as First Process (Primary)
    LS->>Second: open app with URL (kAEGetURL)
    Second->>Capture: start ephemeral NSApp, register kAEGetURL handler
    Capture->>Capture: wait for kAEGetURL or 300ms timeout
    alt URL received
        Capture-->>Second: return captured URL
    else timeout/no URL
        Capture-->>Second: return ""
    end
    Second->>First: notifyFirstInstance (args + captured URL)
    First->>First: OnSecondInstanceLaunch callback handles args (including URL)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #5169: Implements darwin captureLaunchURL() and updates notifyFirstInstance() to append captured URL — directly aligns with that issue's objective.

Possibly related PRs

  • #5103: Touches darwin build-tagging and single-instance platform files; closely related to platform-specific single-instance changes.
  • #4712: Modifies macOS URL handling and Apple Event entrypoints in v3/pkg/application — related to URL/AppleEvent handling on macOS.

Suggested labels

Documentation, v3-alpha, Enhancement, MacOS, go, size:L, lgtm

Suggested reviewers

  • atterpac

Poem

🐰 I hopped into Apple Events at night,

I caught a URL tucked out of sight,
From second process I carried the clue,
Now first-instance wakes and knows what to do,
A rabbit relay, swift and light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main fix: addressing macOS SingleInstance with URL scheme by relaying the URL to the first instance, directly referencing issue #5089.
Description check ✅ Passed The PR description is comprehensive, explaining the problem, solution, implementation details, test evidence, and files changed, though some template sections (Type of change checklist, How Has This Been Tested, Test Configuration, and Checklist items) are not filled out.
Linked Issues check ✅ Passed The PR directly addresses issue #5089 by implementing the solution to capture and relay URL scheme launches to the first instance on macOS, matching the expected behavior described in the issue.
Out of Scope Changes check ✅ Passed All changes are in scope and directly related to fixing #5089; the PR explicitly notes that adding LSMultipleInstancesProhibited is out of scope and will be handled separately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/5089-macos-single-instance-url-scheme

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@v3/examples/single-instance-url-scheme/build/darwin/Taskfile.yml`:
- Around line 21-27: The package task currently sets PRODUCTION: "false",
preventing the build task from using production tags/linker flags; update the
package task (the "package" block that calls task: build) to either remove the
PRODUCTION override or set PRODUCTION: "true" so that the called build task
picks up production flags and linker settings (look for the package task's deps
entry that references task: build and adjust the PRODUCTION var accordingly).

In `@v3/examples/single-instance-url-scheme/README.md`:
- Around line 92-96: Update the two fenced log blocks that currently use plain
triple backticks to specify a language (use "text") so markdownlint no longer
flags them; replace the fence markers around the log excerpts beginning with
"[first] OnSecondInstanceLaunch fired" (the block showing url-in-args? = true)
and the second similar block (the one showing url-in-args? = false) to use
```text instead of ``` so both log excerpts are explicitly marked as text.
- Around line 122-124: The README's example command calls lsregister directly
which may not exist on the user's PATH; update the README.md line that currently
shows "lsregister -f bin/single-instance-url-scheme.dev.app" to invoke
lsregister via the LaunchServices support path (i.e., use the full path to the
LaunchServices Support/lsregister binary) so the command works reliably on
macOS.

In `@v3/examples/single-instance-url-scheme/Taskfile.yml`:
- Around line 27-46: Update the trigger and trigger:force task descriptions to
reflect the fixed behavior: remove statements about the URL being dropped and
about relaying os.Args with no URL; instead state that forced second-instance
launches now capture and relay the URL (the second process relays the URL to the
first instance) and that OnSecondInstanceLaunch fires on the first instance with
url-in-args?=true; update the explanatory text in the summary/desc for the
trigger and trigger:force entries and keep the existing examples/commands (refer
to the trigger and trigger:force blocks and their cmds).

In `@v3/pkg/application/single_instance_darwin_url.go`:
- Around line 6-11: The CFLAGS for the cgo block do not enable Objective‑C
blocks, and the code uses a block literal with dispatch_after; update the cgo
flags to add -fblocks (e.g., extend the existing "#cgo CFLAGS: -x objective-c"
to include -fblocks) and add an explicit import of dispatch/dispatch.h alongside
the Foundation/Cocoa imports to ensure dispatch_after and block syntax compile
correctly.

In `@v3/release_notes.md`:
- Line 2: Update the release note line that claims NSGlassEffectView is
available on macOS 15.0+ to the correct macOS version (macOS 26) so it reads
that native NSGlassEffectView support for Liquid Glass effects requires macOS 26
with NSVisualEffectView as the fallback; locate the entry mentioning
NSGlassEffectView and NSVisualEffectView (and the PR `#4534` / author `@leaanthony`)
in v3/release_notes.md and change the version string only, leaving the rest of
the wording intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74a71441-db44-4d40-9303-4abdab771ba4

📥 Commits

Reviewing files that changed from the base of the PR and between 103b3de and 11c880a.

⛔ Files ignored due to path filters (1)
  • v3/examples/single-instance-url-scheme/build/appicon.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • v3/examples/single-instance-url-scheme/README.md
  • v3/examples/single-instance-url-scheme/Taskfile.yml
  • v3/examples/single-instance-url-scheme/assets/index.html
  • v3/examples/single-instance-url-scheme/build/Taskfile.yml
  • v3/examples/single-instance-url-scheme/build/darwin/Info.dev.plist
  • v3/examples/single-instance-url-scheme/build/darwin/Info.plist
  • v3/examples/single-instance-url-scheme/build/darwin/Taskfile.yml
  • v3/examples/single-instance-url-scheme/build/darwin/icons.icns
  • v3/examples/single-instance-url-scheme/main.go
  • v3/pkg/application/single_instance.go
  • v3/pkg/application/single_instance_darwin_url.go
  • v3/pkg/application/single_instance_launch_url_other.go
  • v3/release_notes.md

Comment thread v3/examples/single-instance-url-scheme/build/darwin/Taskfile.yml
Comment thread v3/examples/single-instance-url-scheme/README.md Outdated
Comment thread v3/examples/single-instance-url-scheme/README.md Outdated
Comment thread v3/examples/single-instance-url-scheme/Taskfile.yml Outdated
Comment thread v3/pkg/application/single_instance_darwin_url.go Outdated
Comment thread v3/release_notes.md Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a macOS-specific gap in SingleInstance handling where URL-scheme launches (delivered via kAEGetURL Apple Events) could be dropped when a second process exits before NSApplication finishes launching, and adds an example + documentation demonstrating the corrected behavior.

Changes:

  • Add a darwin-only captureLaunchURL() that briefly runs an NSApplication loop to receive kAEGetURL and return the URL.
  • Append any captured URL to SecondInstanceData.Args during the second-instance relay (notifyFirstInstance()), aligning with Windows/Linux behavior.
  • Add/adjust example, docs, and release notes describing the fix and how to reproduce/verify it.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
v3/pkg/application/single_instance_darwin_url.go New darwin implementation to capture URL-scheme launches via Apple Events before second-instance exit.
v3/pkg/application/single_instance_launch_url_other.go New non-darwin stub to keep behavior unchanged on other platforms.
v3/pkg/application/single_instance.go Relay path now attempts to capture a macOS launch URL and append it to args before notifying the first instance.
v3/examples/single-instance-url-scheme/main.go New example app to demonstrate/verify URL relay vs. ApplicationLaunchedWithUrl.
v3/examples/single-instance-url-scheme/assets/index.html Minimal UI to display last URL-related events received by the first instance.
v3/examples/single-instance-url-scheme/README.md Documentation explaining the original issue, the fix, and how to run the repro on macOS.
v3/examples/single-instance-url-scheme/Taskfile.yml Helper tasks to run/package the example and trigger URL launches for testing.
v3/examples/single-instance-url-scheme/build/darwin/Taskfile.yml macOS build/package/run scripting for the example app bundle.
v3/examples/single-instance-url-scheme/build/darwin/Info.plist Example app bundle plist registering the custom URL scheme.
v3/examples/single-instance-url-scheme/build/darwin/Info.dev.plist Dev variant plist for the example bundle identifier.
v3/examples/single-instance-url-scheme/build/Taskfile.yml Shared build helper (go mod tidy) for the example.
v3/release_notes.md Release note entry describing the macOS SingleInstance URL relay fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +6 to +7
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

The new darwin CGO file doesn’t set a macOS deployment target in #cgo CFLAGS/LDFLAGS. Other darwin CGO files in this package consistently include -mmacosx-version-min=10.13 in both, and omitting it can change the produced binary’s min OS version and lead to inconsistent linking across files. Consider adding the same -mmacosx-version-min flags here for consistency with the rest of v3/pkg/application darwin sources.

Suggested change
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.13
#cgo LDFLAGS: -mmacosx-version-min=10.13 -framework Foundation -framework Cocoa

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — added -mmacosx-version-min=10.13 to both CFLAGS and LDFLAGS, matching the rest of the darwin files in pkg/application.

Comment on lines +178 to +185
args := os.Args
// On macOS, URL-scheme launches deliver the URL via NSAppleEventManager
// (kAEGetURL), not via argv. Capture it here before we exit so the first
// instance receives the URL in SecondInstanceData.Args — matching the
// Windows/Linux behaviour where the URL is already in argv.
if url := captureLaunchURL(); url != "" {
args = append(args, url)
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

notifyFirstInstance() now calls captureLaunchURL() unconditionally on macOS. When no URL Apple Event arrives, this blocks for the full timeout (currently 300ms) before notifying the first instance, which regresses the “reopen to focus existing instance” path latency for normal launches. Consider reducing the timeout and/or gating URL capture behind a cheap heuristic/config option so non-URL second-instance launches don’t pay the extra delay.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Won't change for now. The 300 ms wait is inherent to the fix — you can't determine whether a URL Apple Event is pending without running [NSApp run], which is the exact step that was missing before. The second-instance path is rare (user explicitly opening a second window of a running app), and 300 ms to bring the existing window forward is imperceptible in practice. Making the timeout configurable is a reasonable follow-up but adds complexity for marginal benefit right now.

Comment on lines +99 to +104
// captureLaunchURL briefly runs an NSApplication event loop so that
// LaunchServices can deliver any pending kAEGetURL Apple Event (e.g. when
// this process was force-launched via "open -n URL").
// Returns the URL string, or "" if none arrived within the timeout.
func captureLaunchURL() string {
cURL := C.CaptureLaunchURL(C.double(0.3))
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

The URL capture timeout is currently a hard-coded literal (0.3) at the call site. It would be easier to tune and reason about (and keep in sync with docs/release notes) if this were a named constant (or derived from a shared setting) rather than a magic number.

Suggested change
// captureLaunchURL briefly runs an NSApplication event loop so that
// LaunchServices can deliver any pending kAEGetURL Apple Event (e.g. when
// this process was force-launched via "open -n URL").
// Returns the URL string, or "" if none arrived within the timeout.
func captureLaunchURL() string {
cURL := C.CaptureLaunchURL(C.double(0.3))
const launchURLCaptureTimeoutSeconds = 0.3
// captureLaunchURL briefly runs an NSApplication event loop so that
// LaunchServices can deliver any pending kAEGetURL Apple Event (e.g. when
// this process was force-launched via "open -n URL").
// Returns the URL string, or "" if none arrived within the timeout.
func captureLaunchURL() string {
cURL := C.CaptureLaunchURL(C.double(launchURLCaptureTimeoutSeconds))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — extracted as launchURLCaptureTimeout = 0.3 constant.

Comment on lines +30 to +44
On macOS 14/15, this routes the URL to a second process (which then hits the
SingleInstance lock and exits), dropping the URL — the bug in action.
On macOS 26+, LaunchServices sends the Apple Event to the already-running
process instead, so ApplicationLaunchedWithUrl fires correctly (no bug seen).
Use trigger:force to reproduce the bug on macOS 26+.
cmds:
- 'open "{{.URL | default "wails-single-url://hello?n=1"}}"'

trigger:force:
summary: 'Force a new process launch with URL (reproduces bug on macOS 26+). Usage: wails3 task trigger:force URL=wails-single-url://hello?n=1'
desc: |
Uses open -n to bypass LaunchServices reuse behaviour and force a new process.
The new process hits the SingleInstance lock, relays os.Args (no URL), and exits.
The Apple Event carrying the URL is queued for the dying process and dropped.
OnSecondInstanceLaunch fires on the first instance with url-in-args?=false.
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

The trigger task description says the URL is dropped on macOS 14/15 (“bug in action”), but with this PR applied the URL should be relayed to the first instance. Consider updating the task description to reflect the fixed behaviour (and, if useful, note that this was the pre-fix behaviour).

Suggested change
On macOS 14/15, this routes the URL to a second process (which then hits the
SingleInstance lock and exits), dropping the URL — the bug in action.
On macOS 26+, LaunchServices sends the Apple Event to the already-running
process instead, so ApplicationLaunchedWithUrl fires correctly (no bug seen).
Use trigger:force to reproduce the bug on macOS 26+.
cmds:
- 'open "{{.URL | default "wails-single-url://hello?n=1"}}"'
trigger:force:
summary: 'Force a new process launch with URL (reproduces bug on macOS 26+). Usage: wails3 task trigger:force URL=wails-single-url://hello?n=1'
desc: |
Uses open -n to bypass LaunchServices reuse behaviour and force a new process.
The new process hits the SingleInstance lock, relays os.Args (no URL), and exits.
The Apple Event carrying the URL is queued for the dying process and dropped.
OnSecondInstanceLaunch fires on the first instance with url-in-args?=false.
Launch the custom URL against the running app.
On macOS 14/15, the second process now hits the SingleInstance lock, relays the
URL to the first instance, and exits, so the already-running app receives it.
On macOS 26+, LaunchServices sends the Apple Event to the already-running
process directly, so ApplicationLaunchedWithUrl also fires correctly there.
Previously, on macOS 14/15, the URL could be dropped; use trigger:force to
exercise the forced-new-process path explicitly.
cmds:
- 'open "{{.URL | default "wails-single-url://hello?n=1"}}"'
trigger:force:
summary: 'Force a new process launch with URL. Usage: wails3 task trigger:force URL=wails-single-url://hello?n=1'
desc: |
Uses open -n to bypass LaunchServices reuse behaviour and force a new process.
The new process hits the SingleInstance lock, relays the launch/URL information
to the first instance, and exits.
Use this to exercise the forced-new-process path explicitly, especially on
macOS 26+ where LaunchServices would normally reuse the running process.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Already addressed in the previous commit — trigger description now describes the fixed relay behaviour.

Comment on lines +42 to +44
The new process hits the SingleInstance lock, relays os.Args (no URL), and exits.
The Apple Event carrying the URL is queued for the dying process and dropped.
OnSecondInstanceLaunch fires on the first instance with url-in-args?=false.
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

The trigger:force description still states the Apple Event is “queued for the dying process and dropped” and that the first instance sees url-in-args?=false. With the new URL-capture relay in place, the expected outcome should be that the URL is captured and appended to SecondInstanceData.Args. Please update this description to match the post-fix behaviour (or explicitly label it as the pre-fix expectation).

Suggested change
The new process hits the SingleInstance lock, relays os.Args (no URL), and exits.
The Apple Event carrying the URL is queued for the dying process and dropped.
OnSecondInstanceLaunch fires on the first instance with url-in-args?=false.
The new process hits the SingleInstance lock, relays its launch data, and exits.
With the URL-capture relay in place, the URL is captured and appended to
SecondInstanceData.Args rather than being dropped with the dying process.
OnSecondInstanceLaunch fires on the first instance with the URL present in args.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Already addressed in the previous commit — trigger:force description now reflects fixed behaviour (url-in-args?=true).

The Apple Event carrying the URL is queued for the dying process and dropped.
OnSecondInstanceLaunch fires on the first instance with url-in-args?=false.
cmds:
- 'open -n -u "{{.URL | default "wails-single-url://hello?n=1"}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"'
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

open -n -u <url> <appbundle> is likely not doing what’s intended: -u treats subsequent arguments as URLs, so the .app path can be interpreted as a URL rather than selecting the target application. To force a new instance and route the URL to this bundle, use open -n -a <appbundle> <url> (or an equivalent -a/-b form).

Suggested change
- 'open -n -u "{{.URL | default "wails-single-url://hello?n=1"}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"'
- 'open -n -a "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" "{{.URL | default "wails-single-url://hello?n=1"}}"'

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — -u is wrong here. Fixed to open -n -a "${APP}" "${URL}". Retested on macOS 26 Apple Silicon: second instance spawns, captures the URL, and the first instance logs url-in-args?=true.

leaanthony and others added 2 commits April 18, 2026 20:40
- Add -fblocks to CGo CFLAGS and explicit dispatch/dispatch.h import
- Fix package task PRODUCTION flag (false→true)
- Use ```text fences for log blocks in README
- Use full lsregister path in README
- Update trigger/trigger:force descriptions to reflect fixed behaviour
- Fix release_notes macOS version: 15.0+ → 26+ for NSGlassEffectView
- Remove #5089 fix entry from release_notes (changelog is automated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add -mmacosx-version-min=10.13 to CGo CFLAGS/LDFLAGS (consistency with rest of pkg/application)
- Extract launchURLCaptureTimeout named constant (replaces magic 0.3 literal)
- Fix trigger:force command: open -n -u → open -n -a (Copilot correctly flagged -u as wrong flag)

Retested after changes: open -n -a correctly forces a new process and URL relay
works (url-in-args?=true confirmed on macOS 26 Apple Silicon).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@leaanthony leaanthony changed the base branch from v3-alpha to master April 29, 2026 13:08
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.

[v3] MacOS single instance lock not compatible with OpenedWithURL event.

3 participants