zed: Wait for session restore before handling CLI open requests (#61814)

Closes #61346

# Objective

At startup, a synchronous `try_recv` decides whether to restore the
session. On a macOS cold launch the CLI request arrives via
`application:openURLs:`, which AppKit dispatches only *after* the
startup closure returns, so the check finds nothing, session restore
begins, and the request lands moments later on the post-startup path.

Both behaviors are individually correct: a user with
`restore_on_startup: "last_session"` wants their session back, and the
requested file should open. The defect is that they race, the CLI open
runs its existing-window matching before restore has placed any windows,
finds nothing to reuse, and opens a redundant window.

## Solution

Make the post-startup open loop wait for restore to settle before
handling a request, so `find_existing_workspace` sees the restored
windows and honors `cli_default_open_behavior`.

The wait races full restore completion against the first window being
placed, so a slow or hung remote-workspace restore can't block a local
open. No timeouts involved, the fix is ordering, not delay :).

## Testing

**Steps**:
1. disable & enable the setting `close windows when quiting an
application` in macos itself in `desktop & dock` section
2. create a bundled Zed app
3. install cli binary to actually test the change
4. Follow any of the steps inside the before/after video's to test all
the cases we have

I tested this with a manual prod build using a custom script that
bundles Zed as a preview app. So I could actually test the cli baviour
since it works different for non-bundled builds. So the easiest way to
test this would be to run bundling on this PR and install the cli binary
to actually test it with the fix included.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase

<details>

<summary>**Disabled** Close windows when quiting an
application</summary>

**before** (disabled apps on close & `add to existing window` cli
bahavior)


https://github.com/user-attachments/assets/b497130b-3432-4fa8-9fbe-70c6f63d165e

**After** (disabled apps on close & `add to existing window` cli
bahavior)


https://github.com/user-attachments/assets/354657f7-8dfe-4beb-9dd4-2bb615cb912c

**After** (disabled apps on close & `open a new window` cli bahavior)


https://github.com/user-attachments/assets/07fc3062-89a3-4e2a-811e-296533feaad7

</details>

<details>

<summary>**Enabled** Close windows when quiting an application
(unchanged)</summary>

**After** (enabled** apps on close & `add to existing window` cli
bahavior (**unchanged**))


https://github.com/user-attachments/assets/445f1d3a-2987-46b1-9bfb-b2d61894ddec

**After** (enabled apps on close & `open a new window` cli bahavior
(**unchanged**))


https://github.com/user-attachments/assets/c3078b0d-7fb4-42db-9dea-095439e23830

</details>

---

Release Notes:

- Fixed an extra window opening on macOS when launching Zed from the CLI
with session restore enabled
This commit is contained in:
Remco Smits authored and GitHub committed 2026-07-30 17:34:51 +00:00
1 parent 5786fee55a
commit 08994c411c
1 file changed
+22 -2
+22 -2
View File
@@ -26,7 +26,7 @@ use db::kvp::{GlobalKeyValueStore, KeyValueStore};
use editor::Editor;
use extension::ExtensionHostProxy;
use fs::{Fs, RealFs};
use futures::{StreamExt, channel::oneshot, future};
use futures::{FutureExt, StreamExt, channel::oneshot, future};
use git::GitHostingProviderRegistry;
use git_ui::clone::clone_and_open;
use gpui::{
@@ -945,11 +945,22 @@ fn main() {
}),
};
let (first_window_tx, first_window_rx) = oneshot::channel::<()>();
let first_window_tx = Rc::new(RefCell::new(Some(first_window_tx)));
let _first_window_subscription = cx.observe_new::<MultiWorkspace>(move |_, _, _| {
if let Some(tx) = first_window_tx.borrow_mut().take() {
tx.send(()).ok();
}
});
let restore_finished = cx.background_spawn(restore_task).shared();
cx.spawn({
let db = workspace::WorkspaceDb::global(cx);
let fs = app_state.fs.clone();
let restore_finished = restore_finished.clone();
async move |_cx| {
restore_task.await;
restore_finished.await;
db.garbage_collect_workspaces(
fs.as_ref(),
&current_session_id,
@@ -965,7 +976,16 @@ fn main() {
component_preview::init(app_state.clone(), cx);
cx.spawn(async move |cx| {
let _first_window_subscription = _first_window_subscription;
let first_window_placed = first_window_rx.shared();
while let Some(urls) = open_rx.next().await {
// On a macOS cold launch, `zed <path>` arrives here after startup already
// began restoring the session, so wait for a restored window to exist before
// matching. Otherwise this open sees no windows and spawns a redundant one (#61346).
futures::select_biased! {
_ = restore_finished.clone() => {}
_ = first_window_placed.clone() => {}
}
cx.update(|cx| {
if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
handle_open_request(request, app_state.clone(), cx);