Skip to content

repl: Add WSL and SSH remote kernel support#47891

Merged
Veykril merged 45 commits intozed-industries:mainfrom
MostlyKIGuess:feature/clean-repl-ssh
Feb 20, 2026
Merged

repl: Add WSL and SSH remote kernel support#47891
Veykril merged 45 commits intozed-industries:mainfrom
MostlyKIGuess:feature/clean-repl-ssh

Conversation

@MostlyKIGuess
Copy link
Contributor

@MostlyKIGuess MostlyKIGuess commented Jan 28, 2026

Closes #15196, #46918

  • fix: notebook_ui, use buffer so that notebooks open in remote/WSL settings.
  • fix: add musl in nix for cross-compilation, without this remote server doesn't build inside NixOS

Release Notes:

  • Implement WSL and SSH remote kernels (crates/repl/src/kernels/*) and wire up spawn/kill kernel proto messages and client requests.

- Implement WSL and SSH remote kernels (crates/repl/src/kernels/*) and
  wire up spawn/kill kernel proto messages and client requests.
- fix: notebook_ui, use buffer so that notebooks open in remote/WSL
  settings.
- fix: add musl in nix for cross-compilation, without this remote server
  doesn't build inside NixOS
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Jan 28, 2026
@MrSubidubi MrSubidubi changed the title repl: add WSL and SSH remote kernel support repl: Add WSL and SSH remote kernel support Jan 28, 2026
@MostlyKIGuess MostlyKIGuess marked this pull request as draft January 28, 2026 18:37
- remove unused worktree_store
- change smol timer to cx.background_executor
- on_action listeners using _: &ActionType instead of &ActionType which
  was being interpreted as variable binding
- removed useless Entity::from() in sessions.rs
@MostlyKIGuess MostlyKIGuess marked this pull request as ready for review January 28, 2026 19:01
})
.await
.log_err()?;
.await;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could all be

            .await
            .inspect_err(|e| {
                log::warn!(
                    "RemoteToolchainStore::list_toolchains: RPC failed for language {}: {:?}",
                    language_name,
                    e
                );
            })
            .ok()?;

Some(envelope.payload.working_directory)
};

// Spawn kernel (Assuming python for now, or we'd need to parse kernelspec logic here or pass the command)
Copy link
Collaborator

Choose a reason for hiding this comment

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

True, we're probably going to have to provide the listing based on what's available on the remote as well as be able to launch using those as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was hesitant to wire up full remote kernel discovery for now, felt like it might bloat this PR too much.
Do you think we should block this until we have proper spec listing, or is the python3 fallback okay for an initial merge?

Copy link
Member

Choose a reason for hiding this comment

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

Note that python3 won't work on windows by default as its just python there

.map(|toolchain| {
background_executor.spawn(async move {
// For remote projects, we assume python is available assuming toolchain is reported.
// We can skip the `ipykernel` check or run it remotely.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We probably do still need to do some kind of ipykernel check. I think in some cases people want the same flow as VS Code where they can install ipykernel. That doesn't always work in every env (oddly even in my uv sync created venvs it has trouble).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we add a prompt to install ipykernel if the selected kernel is missing it? that sort of reflects the notification workflow from VSCode.. I would want to investigate why it's not working on uv venv..

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm sorry I meant that VS Code runs into a problem with a fresh uv sync'ed repo and is unable to install into it. Maybe VS Code has to detect that it's a uv backed repo that needs to use uv pip install ipykernel (or uv add if intended to be kept).

}
}
})
.detach();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This again reminds me we need .split() on zmq.rs so we don't have to create so many background processes and can send while receiving on all the channels.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, had to fight socket ownership here, once zmq adds split this would allow us to drop multiple spawners

Copy link
Collaborator

Choose a reason for hiding this comment

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

I started on that here: zeromq/zmq.rs#194

Probably easier to maintain in runtimed, zmq.rs, and even Zed to just use the Tokio setup that Zed now has inside (my work predates that).

Copy link
Collaborator

Choose a reason for hiding this comment

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

.split() is now supported in here as of #48823 so this should nicely clean up.

Copy link
Member

@Veykril Veykril left a comment

Choose a reason for hiding this comment

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

This does not seem to support docker and also I don't quite understand why we have to special case between different remote types here. Ideally all the specifics of ssh and wsl should be hidden away behind our RemoteConnectionOptions and such. (I have only skimmed over the ssh/wsl modules here for now, so do tell the reasons for the split here)

I am also confused by the fact that we now have a Remote variant but also an ssh and wsl remote variant each

Comment on lines +687 to +694
.log_err()?;
.inspect_err(|e| {
log::warn!(
"RemoteToolchainStore::list_toolchains: RPC failed for language {}: {:?}",
language_name,
e
);
})
.ok()?;
Copy link
Member

Choose a reason for hiding this comment

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

This should remain a log_err imo

Some(envelope.payload.working_directory)
};

// Spawn kernel (Assuming python for now, or we'd need to parse kernelspec logic here or pass the command)
Copy link
Member

Choose a reason for hiding this comment

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

Note that python3 won't work on windows by default as its just python there

let child = this.update(&mut cx, |this, _| this.kernels.remove(&kernel_id));
if let Some(mut child) = child {
child.kill().log_err();
let _ = child.status().await; // Perhaps kill should be enough, should we wait?
Copy link
Member

Choose a reason for hiding this comment

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

kill should suffice, no need to wait since we don't make any use of the return value here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I will remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handled the python3 by having both python and python3 and removed this

Comment on lines +164 to +165
KernelSpecification::SshRemote(_) => (kernelspec.name(), "SSH Remote", None),
KernelSpecification::WslRemote(_) => (kernelspec.name(), "WSL Remote", None),
Copy link
Member

Choose a reason for hiding this comment

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

Why are these separate from Remote?

@MostlyKIGuess
Copy link
Contributor Author

This does not seem to support docker and also I don't quite understand why we have to special case between different remote types here. Ideally all the specifics of ssh and wsl should be hidden away behind our RemoteConnectionOptions and such. (I have only skimmed over the ssh/wsl modules here for now, so do tell the reasons for the split here)

I am also confused by the fact that we now have a Remote variant but also an ssh and wsl remote variant each

Yeah, the naming is a bit overloaded right now.

The simpler mental model I have right now is:

  1. KernelSpecification::Remote - This refers to a Jupyter Server which will be HTTP/WebSocket connection, further used to connect to an existing local/remote JupyterLab instance. We can probably rename it to JupyterServer to avoid confusion with Zed's "Remote Projects"
  2. SSH vs WSL: SshRunningKernel mostly holds a network client while WslRunningKernel holds the execution logic which acts as a process wrapper. I experimented a bit with this in the feature/repl-ssh branch of mine before making this PR..this allows us to directly make use of WSL2's localhost forwarding rather than SSH tunnels.

While we can do this under the same RemoteConnectionOptions because the underlying implementation paths are distinct enough, ( RPC vs Command::new('wsl')), I thought the separation variation would be cleaner.

For the docker support, I was planning for a follow=up PR to keep this one focused to the issues on getting WSL stable first. Implementation for docker should mirror the WSL approach.

I can change the KernelSpecification::Remote to JupyterServer for a clearer distinction.

And to answer the
#47891 (comment)

Why are these separate from Remote?

This ties back to the above discussion where because SshRemote and WslRemote are managed processes, they don't have a URL to show the user. Essentially the Remote here would mean to connect a "Jupyter Server via HTTP/WebSocket" which is why it also has the URL. But Ssh and Wsl Remote refer to the kernels managed by Zed ( via SSH RPC or WSL Process ).

Renaming Remote to JupyterServer would make this more clear..

@MostlyKIGuess
Copy link
Contributor Author

Also one more question.. when you say docker you mean the new Dev Containers support right?
Related Blog: https://zed.dev/blog/dev-containers

@Veykril
Copy link
Member

Veykril commented Jan 29, 2026

Yea lets rename that variant then, and yes i the dev-containers. Absolutely fine to keep that out of this PR for now though

@MostlyKIGuess
Copy link
Contributor Author

MostlyKIGuess commented Jan 29, 2026

Changed the original body to not reflect the NixOS dev build fix and Jupyter View additions as they are dev and behind feature flag respectively.

@rgbkrk
Copy link
Collaborator

rgbkrk commented Jan 29, 2026

KernelSpecification::Remote - This refers to a Jupyter Server which will be HTTP/WebSocket connection, further used to connect to an existing local/remote JupyterLab instance. We can probably rename it to JupyterServer to avoid confusion with Zed's "Remote Projects"

Let's incorporate JupyterHTTPServer into the naming so it's clear what it's connecting to, since they can be doing it locally or in the cloud. That way remote refers to the Zed concept of it.

Replace manual starts_with/slicing with strip_prefix when detecting
\\wsl$ and \\wsl.localhost UNC prefixes
@MostlyKIGuess
Copy link
Contributor Author

Not sure why the remote.workspace error was present, we do need it when using repl over ssh.

- Instead of relying on python and python3, using toolchain path to get
  the proper envrionment
- update with the new repl menu in remote as well
@MostlyKIGuess MostlyKIGuess force-pushed the feature/clean-repl-ssh branch 2 times, most recently from e0114fd to 022816b Compare February 13, 2026 10:12
@MostlyKIGuess MostlyKIGuess marked this pull request as ready for review February 13, 2026 12:23
@MostlyKIGuess MostlyKIGuess marked this pull request as draft February 14, 2026 20:21
@MostlyKIGuess MostlyKIGuess marked this pull request as ready for review February 14, 2026 21:48
@Veykril Veykril self-assigned this Feb 20, 2026
Copy link
Member

@Veykril Veykril left a comment

Choose a reason for hiding this comment

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

one more thing but other lgtm

Comment on lines +1226 to +1229
"-o".to_string(),
"BatchMode=yes".to_string(),
"-o".to_string(),
"ConnectTimeout=10".to_string(),
Copy link
Member

Choose a reason for hiding this comment

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

I worry about this change, we haven't needed this until now for anything else in the ssh layer so I fear this may cause unexpected issues. Do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, I kept this as my connection was unstable, not sure if we need it. Anyhow, if the kernel hangs, we can handle timeouts at the SshRunningKernel level later. I was able to make it work without it anyways

.map(|workspace| workspace.read(cx).project().read(cx).is_local())
.map(|workspace| {
let project = workspace.read(cx).project().read(cx);
project.is_local() || project.is_remote()
Copy link
Member

Choose a reason for hiding this comment

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

This is always true as is_remote is just !is_local, I think you want !project.is_via_collab() (the methods here need some better documentation ....)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, thank you for this! Merged laxly when repl menu was added to upstream, didn't realize this was A or not A by making this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will make the same changes on repl session UI as well

previous check on menu logic was A or not A, allow changing for when not
via collab
revert back SSH args, if kernel suffers hangs, will handle this in
SshRunningKernel
@Veykril Veykril enabled auto-merge (squash) February 20, 2026 08:16
Copy link
Member

@Veykril Veykril left a comment

Choose a reason for hiding this comment

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

Thanks!

auto-merge was automatically disabled February 20, 2026 08:42

Head branch was pushed to by a user without write access

@Veykril Veykril enabled auto-merge (squash) February 20, 2026 09:24
@Veykril Veykril merged commit ae9bb6a into zed-industries:main Feb 20, 2026
27 checks passed
@MostlyKIGuess
Copy link
Contributor Author

btw @Veykril, I was going through the remoting 1.0 notes and dev-containers notes before actually starting on Dev Containers implementation. A lot of what I'll have to do will need to answer some of the open questions in those notes on how to go about this, would you be free to discuss an approach?

Port handling is the core piece here and from the notes it looks like it could be some effort just to break the DevContainer CLI dependency before we can even get to the forwarding itself. I'd rather have team consensus on that before implementing anything. Could also just break it down into smaller PRs targeting the noted issues you have first and later on bring the Python kernel once the ground is solid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:repl repl, jupyter, notebooks, etc cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remote Project REPL support

3 participants