Skip to content

fix: prevent duplicate window when opening external URL on cold start#217

Merged
iamEvanYT merged 4 commits intomainfrom
opencode/fix-duplicate-window-on-cold-start-url
Mar 2, 2026
Merged

fix: prevent duplicate window when opening external URL on cold start#217
iamEvanYT merged 4 commits intomainfrom
opencode/fix-duplicate-window-on-cold-start-url

Conversation

@iamEvanYT
Copy link
Member

Summary

Fixes a bug where opening a link from another app (while Flow is set as default browser and is closed) creates two windows instead of opening the URL as a tab in the restored session window.

Root Cause

When the app cold-starts via an external URL, two independent async paths race with no coordination in browser.ts:

  1. processInitialUrl() (or macOS open-url event) → handleOpenUrl() sees 0 windows → creates Window Custom Page Load Error #1
  2. runOnboardingOrInitialWindow()restoreSession() → creates Window Redesign #2 from persisted tabs

Both run concurrently, neither knows about the other, and both independently create windows.

Fix

  • src/main/app/urls.ts: Added a startup URL queue. During cold start (startupComplete === false), handleOpenUrl() queues URLs instead of creating windows immediately. New flushPendingUrls() function sets the flag and opens all queued URLs using the already-existing restored windows.
  • src/main/app/onboarding.ts: After session restore completes, calls flushPendingUrls() so queued URLs open as new tabs in the restored window. Also properly awaits createInitialWindow() which was previously fire-and-forget.

Behavior After Fix

Scenario Before After
Cold start via external URL (with saved session) 2 windows 1 window (URL opens as tab in restored window)
Cold start via external URL (no saved session) 1 window 1 window (no change)
URL opened while app is already running 1 window 1 window (no change — startupComplete is already true)

When the app is closed and set as default browser, clicking a link in
another app caused two windows to appear: one from the URL handler and
one from session restore. This happened because processInitialUrl() /
open-url and restoreSession() both independently detected zero windows
and created their own.

Queue external URLs received during startup instead of handling them
immediately. After session restore (or initial window creation)
completes, flush the queue so the URL opens as a new tab in the
already-restored window.

Also fixes restoreSession() not being awaited in the onboarding flow,
which could have caused further timing issues.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

Build artifacts for all platforms are ready! 🚀

Download the artifacts for:

One-line installer (Unstable):
bunx flow-debug-build --open 22586327955

(execution 22586327955 / attempt 1)

@greptile-apps
Copy link

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR fixes a race condition where opening an external URL during cold start created two windows instead of one. The fix introduces a queueing mechanism: URLs received before startup completes are queued and then flushed after the initial window (either onboarding or session restore) is created, ensuring they open as tabs in the existing window.

Key changes:

  • urls.ts: Added pendingStartupUrls queue and startupComplete flag to coordinate URL handling during startup
  • onboarding.ts: Calls flushPendingUrls() after window creation and properly awaits createInitialWindow() to ensure synchronization

The solution elegantly coordinates two previously-independent async paths (processInitialUrl()/open-url event and runOnboardingOrInitialWindow()) that were racing to create windows.

Confidence Score: 5/5

  • Safe to merge with no issues found
  • The implementation is clean, well-documented, and correctly addresses the race condition. The queueing mechanism is thread-safe (JavaScript is single-threaded), error paths are handled appropriately, and the await addition ensures proper sequencing. The code changes are focused and minimal.
  • No files require special attention

Important Files Changed

Filename Overview
src/main/app/urls.ts Added URL queueing mechanism with pendingStartupUrls and startupComplete flag to prevent race condition during cold start
src/main/app/onboarding.ts Added flushPendingUrls() calls after window creation and properly awaits createInitialWindow() to ensure window exists before processing queued URLs

Sequence Diagram

sequenceDiagram
    participant CLI as Command Line / OS
    participant URL as urls.ts
    participant OB as onboarding.ts
    participant WC as Window Controller
    
    Note over CLI,WC: Before Fix (Race Condition)
    CLI->>URL: processInitialUrl()
    CLI->>OB: runOnboardingOrInitialWindow()
    par Parallel Execution
        URL->>WC: handleOpenUrl() sees 0 windows
        WC->>WC: Create Window #1 for URL
    and
        OB->>WC: restoreSession()
        WC->>WC: Create Window #2 from session
    end
    Note over CLI,WC: Result: 2 windows created
    
    Note over CLI,WC: After Fix (Coordinated)
    CLI->>URL: processInitialUrl()
    CLI->>OB: runOnboardingOrInitialWindow()
    URL->>URL: handleOpenUrl() checks startupComplete=false
    URL->>URL: Queue URL in pendingStartupUrls
    OB->>WC: await restoreSession()
    WC->>WC: Create Window from session
    OB->>URL: flushPendingUrls()
    URL->>URL: Set startupComplete=true
    URL->>WC: Open queued URLs as tabs in existing window
    Note over CLI,WC: Result: 1 window with URL as tab
Loading

Last reviewed commit: a150491

Replace flushPendingUrls() with discardPendingUrls() in the onboarding
code paths so no browser windows are created while onboarding is active.
The new helper marks startup as complete (so subsequent open-url events
work normally) but drops any queued URLs silently.
Previously only queued startup URLs were discarded; URLs arriving after
discardPendingUrls() (via open-url, continue-activity, etc.) would still
create browser windows alongside the onboarding window.

Add an onboardingActive flag that causes handleOpenUrl() to silently
drop every incoming URL while onboarding is in progress. The flag is
cleared in the onboarding:finish IPC handler so normal URL handling
resumes once the user completes onboarding.
…tive flag

Replace the manual onboardingActive flag and setOnboardingComplete()
with a check against the existing hasCompletedOnboarding() in
handleOpenUrl(). This leverages the cached onboarding state that is
already the source of truth elsewhere, removing redundant state and
the need to hook into the onboarding:finish IPC handler.
@iamEvanYT iamEvanYT merged commit f07114a into main Mar 2, 2026
8 checks passed
@iamEvanYT iamEvanYT deleted the opencode/fix-duplicate-window-on-cold-start-url branch March 2, 2026 17:23
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