Skip to content

fix(desktop): forward second-instance deep links to the running app - #7180

Closed
maslinedwin wants to merge 6 commits into
pingdotgg:mainfrom
maslinedwin:fix/second-instance-deeplink
Closed

maslinedwin wants to merge 6 commits into
pingdotgg:mainfrom
maslinedwin:fix/second-instance-deeplink

Conversation

@maslinedwin

@maslinedwin maslinedwin commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5978. Second-instance argv and macOS open-url now extract t3code:// URLs and dispatch them instead of only focusing the window.


Note

Medium Risk
Touches OAuth/SSO routing, macOS open-url handling, and main-window creation races; mistakes could drop callbacks, load links on the wrong window, or regress WSL splash behavior.

Overview
Forwards custom-scheme deep links (t3code / t3code-dev) into the already-running desktop app instead of only focusing a window.

Adds desktopProtocolUrl: parse URLs from argv (last match wins), queue/apply a single pending link when no registered main exists, and per-window loaders so dev load retries keep the deep link instead of reverting to home.

DesktopClerk.configure now handles second-instance argv (Windows/Linux) and open-url (macOS, with preventDefault) via revealAndDispatch: load on electronWindow.main only (not the WSL splash), queue + createMainIfBackendReady when needed, and reveal with no URL when argv has no link.

DesktopWindow consumes the pending URL on first main load, tracks currentLoadUrl for retries/recovery, resets to app home after a deep link did-finish-load, and uses a Semaphore on createMain / ensureMain / createMainIfBackendReady so concurrent deep-link and backend-ready paths cannot open duplicate mains.

Reviewed by Cursor Bugbot for commit 7087b0d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Forward deep-link URLs to the running desktop app on second-instance and open-url events

  • On Windows/Linux second-instance events, extracts a custom-scheme URL from argv and loads it in the existing main window via new helpers in desktopProtocolUrl.ts.
  • On macOS, handles the open-url app event in DesktopClerk.ts, calls preventDefault(), and dispatches the URL the same way.
  • When no main window exists yet, the URL is queued and applied after the main window is created; stale queued URLs are replaced by newer ones.
  • DesktopWindow.ts now tracks the current deep-link URL, resets to the home URL after the deep link finishes loading, and serializes main window creation to prevent duplicate windows during races.
  • Risk: main window creation paths (createMain, ensureMain, createMainIfBackendReady) are now gated by a semaphore, which changes their concurrency behavior.

Macroscope summarized 7087b0d.

Fixes pingdotgg#5978. Second-instance argv and macOS open-url now extract t3code:// URLs and dispatch them instead of only focusing the window.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f3a274a-0470-459d-acd4-3c93b3bc33a5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 16, 2026
Comment thread apps/desktop/src/app/DesktopClerk.ts
Comment thread apps/desktop/src/app/DesktopClerk.ts
Comment thread apps/desktop/src/app/DesktopClerk.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces new runtime behavior for forwarding deep links to an already-running desktop instance, including new event handlers (second-instance, open-url), module-level state for queuing pending URLs, and semaphore-based window creation serialization. The scope and behavioral impact warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Cold-launch open-url and WSL splash both left protocol URLs undelivered: the handler returned after preventDefault, or loadURL hit the splash. Queue the latest URL and apply it on the registered main window after createMain.
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Aug 16, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the Effect service changes in DesktopClerk.ts, DesktopWindow.ts, and the new desktopProtocolUrl.ts against the Effect service conventions. One finding: the pending protocol URL is shared between two Effect services through module-level mutable state instead of the Effect environment.

Posted via Macroscope — Effect Service Conventions

Comment on lines +25 to +27
// Latest protocol URL received before a real main window exists (cold launch
// or WSL connecting splash). DesktopWindow.createMain applies it after setMain.
let pendingDesktopProtocolUrl: string | null = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This module-level mutable slot is a hidden channel between two Effect services: DesktopClerk.make writes it via queuePendingDesktopProtocolUrl and DesktopWindow.createMain reads it via applyPendingDesktopProtocolUrl, so the dependency never appears in either service's environment or type (and the tests have to reset it in beforeEach).

Consider holding this state in a Ref created inside DesktopWindow.make (alongside splashWindowRef/backendReadyRef) and exposing something like queuePendingProtocolUrl(url) on the DesktopWindow service; DesktopClerk.configure already acquires yield* DesktopWindow.DesktopWindow, so it can call that instead of the global. The pure helpers (isDesktopProtocolUrl, extractDesktopProtocolUrl, loadDesktopProtocolUrl) can stay in this module.

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. The queue still has to be readable from DesktopWindow.createMain after setMain. I left the helpers in desktopProtocolUrl.ts and serialized the create path in Clerk; moving the slot onto DesktopWindow is a follow-up if you want the dependency in the service type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread apps/desktop/src/app/DesktopClerk.ts Outdated
Comment thread apps/desktop/src/app/DesktopClerk.ts Outdated
Comment thread apps/desktop/src/window/DesktopWindow.ts
Concurrent open-url and second-instance events now share one create
gate so they cannot each open an untracked main window.
Comment thread apps/desktop/src/app/DesktopClerk.ts
Second-instance and open-url callbacks could lose an SSO URL: createMain
loaded home first, development retries reloaded home, and a gate waiter
could apply an older fiber-local URL after a newer one was already queued.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5526327. Configure here.

Comment thread apps/desktop/src/window/DesktopWindow.ts
currentLoadUrl kept the SSO callback after did-finish-load, so renderer
crash recovery replayed the one-shot protocol URL instead of the app home.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue in apps/desktop/src/app/desktopProtocolUrl.ts: service state/behavior is exchanged through module globals rather than the Effect environment. The pending-URL slot was already raised earlier in this PR and acknowledged as a follow-up; the new window-loader registry added since then is flagged below.

Posted via Macroscope — Effect Service Conventions

Comment on lines +23 to +30
const desktopProtocolWindowLoaders = new WeakMap<object, DesktopProtocolWindowLoader>();

export function registerDesktopProtocolWindowLoader(
window: object,
load: DesktopProtocolWindowLoader,
): void {
desktopProtocolWindowLoaders.set(window, load);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This adds a second module-global channel between the two services: DesktopWindow.make writes per-window loaders into this WeakMap and DesktopClerk reaches them indirectly through loadDesktopProtocolUrl, so the dependency is invisible in both service types (and in DesktopWindow's own state, which already keeps window bookkeeping in refs).

Consider owning the "navigate the main window to a URL" behavior on the DesktopWindow service (e.g. a loadProtocolUrl(url) operation whose implementation closes over currentLoadUrl/clearDevelopmentLoadRetry for the window it created) and having DesktopClerk.configure call it via yield* DesktopWindow.DesktopWindow, which it already acquires. isDesktopProtocolUrl / extractDesktopProtocolUrl remain fine as pure helpers here.

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same follow-up as the pending-URL slot. The WeakMap only exists so development retries and crash recovery share the window's intended URL after Clerk applies a deep link. DesktopClerk already acquires DesktopWindow; moving loadProtocolUrl onto that service (and folding the pending slot in with it) is the cleanup if you want the dependency in the service type. Not changing behavior in this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

The Clerk create gate only serialized open-url / second-instance handlers.
handleBackendReady and activate could still pass the splash-filtered empty
check before setMain and open a second untracked main, dropping the queued
OAuth deep link. Share one DesktopWindow permit and re-check before create.
@t3dotgg

t3dotgg commented Aug 28, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

We are keeping OPEN #8246 as the complete review path for desktop thread deep links. This branch forwards second-instance links but does not own the full environment and thread route.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotgg t3dotgg closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: second-instance handler drops deep-link URL, breaking SSO login when app is already running

2 participants