Skip to content

Opening a repo (CLI / second-instance) switches the focused window instead of focusing the window that already has it open #177

@LeaVerou

Description

@LeaVerou

Summary

When a repository is opened via the CLI (github-desktop-plus-cli <path>, which boils down to open -n … --args --cli-open=<path>), the app always routes the open action to the currently-focused window and switches that window's repository — even when another already-open window is showing the requested repository.

In my case it was triggered via an editor-integrated "Open in GitHub Desktop" command (that I have added via this extension), but it's reproducible via the CLI directly.

Compare with the reverse behavior: "Open in Editor", where typically editors will bring up the existing window rather than switch the most recent window to a different project.

Steps to reproduce

  1. Open repo A in one window.
  2. Open repo B in a second window, and leave it focused.
  3. From outside the app, run github-desktop-plus-cli /path/to/A

Expected: the window already showing A is restored/focused.
Actual: the focused window (showing B) is switched to A; the original A window is left as-is.

Investigation

I asked Claude to look into it, these were the results. Happy to prototype a PR, just wanted to make sure there is alignment on the direction.

Where it happens

Both the second-instance handler and the CLI dispatch resolve their target through getTargetWindow(), which returns the focused window (or the first window) — it never considers which window already has the requested repository:

  • The window picker:
    function getTargetWindow() {
    const focusedWindow = BrowserWindow.getFocusedWindow()
    const focusedAppWindow = getAppWindowFromBrowserWindow(focusedWindow)
    if (focusedAppWindow !== null) {
    return focusedAppWindow
    }
    return getAppWindows()[0] ?? null
    }
  • second-instance handler routes through it:
    app.on('second-instance', (event, args, workingDirectory) => {
    // Someone tried to run a second instance, we should focus our window.
    const targetWindow = getTargetWindow()
    if (targetWindow) {
    if (targetWindow.isMinimized()) {
    targetWindow.restore()
    }
    if (!targetWindow.isVisible()) {
    targetWindow.show()
    }
    targetWindow.focus()
    }
    handleCommandLineArguments(args)
    })
  • --cli-open dispatch:
    if (typeof args['cli-open'] === 'string') {
    handleCLIAction({ kind: 'open-repository', path: args['cli-open'] })
  • handleCLIActiononDidLoadgetLoadedTargetWindowgetTargetWindow:
    function handleCLIAction(action: CLIAction) {
    onDidLoad(window => {
    // This manual focus call _shouldn't_ be necessary, but is for Chrome on
    // macOS. See https://github.com/desktop/desktop/issues/973.
    window.focus()
    window.sendCLIAction(action)
    })
    }
    and
    function onDidLoad(fn: OnDidLoadFn) {
    const loadedWindow = getLoadedTargetWindow()
    if (loadedWindow !== null) {
    fn(loadedWindow)
    return
    }
    pendingOnDidLoadFns.push(fn)
    }

I searched the source for any "find the window already showing repo X" logic and there's none — the CLI / second-instance path has no notion of which window holds which repository.

Suggested fix

Before routing open-repository to getTargetWindow(), search the open windows for one whose currently-selected repository matches the requested path; if found, restore/focus that window instead of changing the focused one. Falls back to current behavior when no window has it.

The main process doesn't currently track a window→repository mapping (windows report titles but not their selected repo path), so this likely needs each window to report its selected repository path to main so the lookup can be done there. A path-normalization step (resolve symlinks / trailing slashes / case) would make the match robust.

Environment

  • GitHub Desktop Plus 3.5.7.2
  • macOS Tahoe 26.3 (Apple Silicon)

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedI agree this feature/bug is valid, and will work on it when I get a chance

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions