# Objective
Fixes #60873.
`projects::OpenRemote` has two handling paths:
- The remote-project picker handles `from_existing_connection: false`.
- The active remote workspace handles `from_existing_connection: true`.
The existing-connection path ignored the action’s `create_new_window`
field. Consequently, selecting another project from an active SSH
workspace always created a new OS window—even when:
- the action explicitly specified `create_new_window: false`, or
- `create_new_window` was omitted and `default_open_behavior` was set to
`"existing_window"`.
The expected behavior is:
| `create_new_window` | `default_open_behavior` | Result |
|---|---|---|
| `true` | Any value | Open a new window |
| `false` | Any value | Reuse the current window |
| Not specified | `"new_window"` | Open a new window |
| Not specified | `"existing_window"` | Reuse the current window |
## Root cause
The `from_existing_connection: true` action handler forwarded the
selected paths to `open_new_ssh_project_from_project` without reading
`action.create_new_window`.
That helper then called `open_remote_project` with:
```rust
OpenOptions {
workspace_matching: WorkspaceMatching::None,
..Default::default()
}
```
This left `OpenOptions::requesting_window` as `None`. The remote-opening
path interprets a missing requesting window as a request to create a new
window, so the action always opened one regardless of the action payload
or workspace setting.
## Solution
The existing-connection action handler now resolves `create_new_window`
before starting the asynchronous open operation.
When the action does not provide an explicit value, it uses the same
`WorkspaceSettings::default_open_behavior` fallback as the normal
project-opening path:
```rust
let create_new_window = action.create_new_window.unwrap_or_else(|| {
matches!(
WorkspaceSettings::get_global(cx).default_open_behavior,
DefaultOpenBehavior::NewWindow
)
});
```
The resolved boolean is passed to `open_new_ssh_project_from_project`.
The helper translates that policy into the existing `OpenOptions` API:
- When `create_new_window` is `true`, `requesting_window` remains
`None`.
- When it is `false`, `requesting_window` is set to the current
`MultiWorkspace` window handle.
The existing `WorkspaceMatching::None` behavior is intentionally
preserved. This PR changes only which OS window hosts the opened remote
workspace; it does not change workspace matching, path selection,
connection handling, telemetry, or error behavior.
### Why resolve the setting in the action handler?
The action handler is where the explicit action value and the workspace
setting are both available. Resolving the option there also matches the
neighboring local and remote project-opening flows.
Passing the resolved boolean keeps the helper’s input aligned with the
user-facing policy while allowing the helper to translate it into the
lower-level `requesting_window` representation.
### Why not change `open_remote_project`?
`open_remote_project` is shared by several remote-opening flows.
Inferring an active window inside that function would change the
semantics of unrelated callers that deliberately omit
`requesting_window` to create a new window.
Keeping the change in the existing-connection action path limits the
behavioral change to the reported issue.
### Dependency impact
The regression test needs a real mock remote workspace so that it
exercises the same action registration and remote-client checks as the
application.
The following test-support development dependencies were enabled for the
`zed` crate:
- `fs`, for `FakeFs`
- `remote`, for the mock `RemoteClient`
- `remote_server`, for `HeadlessProject`
These are development-only dependencies and do not change Zed’s runtime
dependency graph. The `Cargo.lock` change records `remote_server` as a
dependency of the `zed` package under the test configuration.
## Testing
Added an end-to-end GPUI regression test:
```text
test_open_remote_from_existing_connection_reuses_window
```
The test:
1. Creates a fake remote filesystem with two project directories.
2. Starts a headless mock remote server.
3. Opens the initial project through `open_remote_project`.
4. Verifies that exactly one window exists.
5. Transitions the mock client to `ServerNotRunning`.
6. Injects `/other-project` as the path-prompt result.
7. Dispatches:
```rust
zed_actions::OpenRemote {
from_existing_connection: true,
create_new_window: Some(false),
}
```
8. Verifies that the application still has exactly one window.
9. Answers the expected failed-reconnection prompt with `Cancel`,
allowing the detached action task to terminate cleanly.
The unavailable-server step keeps the test focused on window routing. It
avoids requiring a second successful mock SSH lifecycle while still
exercising the real action handler, path prompt, remote workspace,
helper, and `open_remote_project` entry point.
Before the production change, the test failed with:
```text
create_new_window: false should reuse the current window
left: 2
right: 1
```
After the change, the test passes.
### Verification performed
Focused regression test:
```sh
cargo test -p zed test_open_remote_from_existing_connection_reuses_window
```
Result:
```text
1 passed; 0 failed
```
GPUI scheduler sweep:
```sh
ITERATIONS=20 cargo test -q -p zed test_open_remote_from_existing_connection_reuses_window -- --nocapture
```
Result:
```text
Seeds 0 through 19 passed
```
Complete `zed` test target, run serially because several existing tests
share global session state:
```sh
cargo test -q -p zed -- --test-threads=1
```
Result:
```text
74 passed; 0 failed; 1 ignored
```
Formatting:
```sh
cargo fmt -p zed --check
```
Repository-prescribed Clippy check:
```sh
./script/clippy -p zed
```
This ran the release, all-targets, all-features configuration with
warnings denied and completed successfully.
The change and test were run on Fedora Linux.
### Manual verification
This can also be verified interactively:
1. Connect to a project over SSH.
2. Bind or dispatch:
```json
[
"projects::OpenRemote",
{
"from_existing_connection": true,
"create_new_window": false
}
]
```
3. Select another directory on the connected host.
4. Confirm that the project opens in the current Zed window.
5. Repeat with `create_new_window: true` and confirm that a new window
is created.
6. Omit `create_new_window` and confirm that the result follows
`default_open_behavior`.
No screenshot is included because this does not change rendered UI; it
changes which window contains the opened remote workspace.
## Scope and impact
- No unsafe code was added.
- No remote protocol or transport behavior changed.
- No settings schema changed.
- No action schema changed.
- No user-facing strings changed.
- No new runtime dependencies were introduced.
- The added work is limited to resolving one optional boolean and
downcasting the current window handle when reuse is requested.
- The window-handle lookup occurs only when this action is dispatched
and has no meaningful performance impact.
## 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)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
---
Release Notes:
- Fixed opening a project from an existing remote connection to respect
the configured window behavior.3 files changedLine totals unavailable