Repository navigation
[Feature]: Support "Open in Zed" for remote (SSH) environments #8938
Description
Summary
The Open picker offers Zed for local environments. It does not offer Zed for remote
environments. Remote mode falls back to VS Code, even when VS Code is not installed.
Zed supports remote SSH projects. It uses a different deep link shape than VS Code.
T3 Code has no code path for that shape.
Steps to reproduce
- Install Zed on the client machine. Do not install VS Code, Cursor, VSCodium, or VS Code Insiders.
- Install Zed on a remote machine. Run the T3 Code server there.
- Open a project locally on the client. The
Openbutton shows Zed. This is correct. - Connect the client to the remote server. Open the same project.
- The
Openbutton shows VS Code. The dropdown lists VS Code only.
Expected
The picker offers Zed in remote mode. Open launches Zed against the remote path over SSH.
Cause
Remote mode filters the editor list to REMOTE_CAPABLE_EDITOR_IDS.
apps/web/src/components/chat/OpenInPicker.tsx:198
const effectiveEditors = remote.mode === "local-exec" ? availableEditors : remoteCapableEditors;REMOTE_CAPABLE_EDITOR_IDS derives from the optional remoteScheme field.
packages/contracts/src/editor.ts:88
export const REMOTE_CAPABLE_EDITOR_IDS: ReadonlyArray<EditorId> = EDITORS.flatMap((editor) =>
remoteSchemeOf(editor) !== undefined ? [editor.id] : [],
);The Zed definition carries no remoteScheme.
packages/contracts/src/editor.ts:52
{ id: "zed", label: "Zed", commands: ["zed", "zeditor"], launchStyle: "direct-path" },So Zed drops out of the remote list. The probe then returns an empty array, and the
hardcoded fallback fills the button.
apps/web/src/remoteOpen.ts:129
const REMOTE_FALLBACK_EDITORS: ReadonlyArray<EditorId> = ["vscode"];Why a one-line fix does not work
buildRemoteOpenUrl hardcodes the VS Code URI shape.
packages/contracts/src/editor.ts:102
return `${scheme}://vscode-remote/ssh-remote+${encodeURIComponent(input.host)}${encodedPath}`;Zed uses a different shape. From the Zed docs:
zed://ssh/[<user>@]<host>[:<port>]/<path>
Adding remoteScheme: "zed" alone produces zed://vscode-remote/ssh-remote+host/path.
Zed does not handle that URL.
Proposed change
Replace the bare remoteScheme string with a descriptor that carries the link shape.
packages/contracts/src/editor.ts
type RemoteLinkStyle = "vscode-remote" | "zed-ssh";
type EditorDefinition = {
// ...
readonly remoteLink?: {
readonly scheme: string;
readonly style: RemoteLinkStyle;
};
};Update the Zed entry:
{
id: "zed",
label: "Zed",
commands: ["zed", "zeditor"],
launchStyle: "direct-path",
remoteLink: { scheme: "zed", style: "zed-ssh" },
},Branch the builder on style. Note the drive-letter rule described under
Verified remote path form.
export const buildRemoteOpenUrl = (input: {
readonly editor: EditorId;
readonly host: string;
readonly absolutePath: string;
}): string | undefined => {
const link = remoteLinkOf(input.editor);
if (link === undefined) {
return undefined;
}
const posixPath = input.absolutePath.replaceAll("\\", "/");
const rootedPath = posixPath.startsWith("/") ? posixPath : `/${posixPath}`;
if (link.style === "zed-ssh") {
// Zed's remote server resolves a rooted path on the system drive, so a
// Windows `C:` prefix must be dropped: /C:/Users/x -> /Users/x
const zedPath = rootedPath.replace(/^\/[Cc]:/, "");
const encodedZedPath = zedPath.split("/").map(encodeURIComponent).join("/");
return `${link.scheme}://ssh/${input.host}${encodedZedPath}`;
}
const encodedPath = rootedPath.split("/").map(encodeURIComponent).join("/");
return `${link.scheme}://vscode-remote/ssh-remote+${encodeURIComponent(input.host)}${encodedPath}`;
};probeRemoteEditors iterates REMOTE_CAPABLE_EDITOR_IDS and checks each editor's
commands. Zed already declares ["zed", "zeditor"]. The probe picks Zed up with no
further change.
The deep link this produces is verified end to end against a Windows remote. See
Verified remote path form.
Fallback option: launch the Zed CLI on the client
Not required, since the deep link works. Recording it as a second route.
probeRemoteEditors already runs on the client and already resolves the zed binary. A
sibling IPC method that spawns zed "ssh://<host>:<path>" reuses that resolution and
skips the OS handler entirely.
Verified remote path form
Client: macOS, Apple Silicon. Remote server: Windows, Zed remote server.
Both routes are confirmed working against the same project.
Deep link, the form the patch above emits:
open "zed://ssh/<host>/Users/<user>/Documents/Code/<Project>"
CLI, scp-style:
zed "ssh://<host>:/Users/<user>/Documents/Code/<Project>"
One rule governs both. The drive letter must be dropped. The project's real Windows
path is C:\Users\<user>\Documents\Code\<Project>. Zed's remote server resolves a
rooted path on the system drive, so it wants /Users/<user>/Documents/Code/<Project>.
Passing C:/Users/... or /C:/Users/... did not work in either route.
The two routes differ in host syntax. The deep link separates host and path with /. The
CLI separates them with :. The patch above targets the deep-link form.
Behavior on non-system drives (D: and up) is untested.
Confirmed userland workaround
The plumbing all works today. Only the URL shape is missing.
ElectronShell.parseSafeExternalUrl already allows vscode://vscode-remote/ssh-remote+…
through to Electron.shell.openExternal, so macOS hands it to whatever app claims the
vscode scheme. Registering a small handler app for that scheme, translating the URL, and
spawning the Zed CLI produces a fully working "Open in Zed" against a Windows remote.
The translation that works:
in: vscode://vscode-remote/ssh-remote+<host>/C%3A/Users/<user>/Documents/Code/<Project>
out: ssh://<host>:/Users/<user>/Documents/Code/<Project>
This confirms the only blocker is buildRemoteOpenUrl plus the missing remoteScheme.
The button label still renders VS Code, because the shim cannot reach the picker state.
That is the part only a code change can fix.
Open questions for maintainers
- Host encoding: the VS Code branch calls
encodeURIComponenton the host. The Zed
host sits in path position afterssh/. Percent-encoding@and:may break Zed's
parser. The draft above leaves the host raw. Please confirm the correct form. - Non-system drives: the drive-letter strip is verified for
C:only. A project on
D:may need the letter preserved, or may need a different form. The regex above
stripsC:alone and leaves other letters intact. - Fallback behavior:
REMOTE_FALLBACK_EDITORSshows VS Code when the probe finds
nothing. This offers an editor the user does not have. A disabled
No remote-capable editor founditem may serve the user better. - CLI presence on macOS: Zed installs its
zedCLI through a separate action. A
Finder-launched T3 Code may not see it on PATH. The existing probe comment already
notes this risk.
Environment
- Client: macOS, Apple Silicon
- Remote server: Windows, running the Zed remote server
- Editor on both machines: Zed
- VS Code: not installed on either machine
- Local mode on either machine offers Zed correctly. Only remote mode fails.
References
- Zed remote development docs: https://github.com/zed-industries/zed/blob/main/docs/src/remote-development.md
Activity
fix(editors): drop the Windows drive letter in Zed remote links
Squashed '.repos/effect-smol/' changes from a43c7e244c..e17ecdbe90