Skip to content

Fix settings schema missing extension-provided LSP settings on remote - #62355

Open
vancez wants to merge 5 commits into
zed-industries:mainfrom
vancez:fix/remote-extension-schema-sync
Open

Fix settings schema missing extension-provided LSP settings on remote#62355
vancez wants to merge 5 commits into
zed-industries:mainfrom
vancez:fix/remote-extension-schema-sync

Conversation

@vancez

@vancez vancez commented Aug 8, 2026

Copy link
Copy Markdown

Objective

Fixes an issue where language servers provided by extensions are not included in the settings schemas served by remote servers. When connecting to a remote server via the open_remote_project_with_existing_connection flow (dev servers, multi-workspace reopen, git worktree picker), setup-N servers never received any extension sync, so their schemas omitted extension-provided language servers, breaking language support for remote development.

Solution

Three changes ensure that language servers provided by extensions are included in the settings schemas served by remote servers:

  1. Register connections opened via open_remote_project_with_existing_connection with the extension store: dev servers, multi-workspace reopen, and the git worktree picker now register their connections with the extension store so that extensions are synced to those servers. Previously only the open_remote_project flow did this, so setup-N servers never received any extension sync and their schemas omitted extension language servers.

  2. Emit ExtensionsInstalledChanged from HeadlessExtensionStore after sync and install complete: this allows the remote process to invalidate its cached settings schemas. The local ExtensionStore already emitted this event; without it, the remote schema cache stayed stale for the lifetime of the server process.

  3. Include available LSP adapters in the project settings schema: this matches the user settings schema (this was fixed for settings in json_schema_store: Include available LSP adapters in settings schema #46766 but never applied to project_settings).

Testing

  • Manually verified that after connecting to a remote server via the open_remote_project_with_existing_connection flow (including dev servers and the git worktree picker), extension language servers appear in the schema served by the remote server.
  • Verified that ExtensionsInstalledChanged is emitted after sync and install complete, and that the remote schema cache is correctly invalidated and rebuilt.
  • Verified that the project_settings schema includes the available LSP adapters, matching the behavior of the user settings schema.
  • Reviewers should focus on the multi-workspace reopen and git worktree picker flows (the two previously unregistered flows) to confirm that extension language servers are correctly synced and appear in the schema.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Showcase

Server / Project — before the fix
image
Local
image


Release Notes:

  • Fixed an issue where language servers provided by extensions were missing from settings schemas served by remote servers.

Three changes ensure that language servers provided by extensions are
included in the settings schemas served by remote servers:

- Register connections opened via open_remote_project_with_existing_connection
  (dev servers, multi-workspace reopen, git worktree picker) with the extension
  store, so that extensions are synced to those servers. Previously only the
  open_remote_project flow did this, so setup-N servers never received any
  extension sync and their schemas omitted extension language servers.

- Emit ExtensionsInstalledChanged from HeadlessExtensionStore after sync and
  install complete, so that the remote process invalidates its cached settings
  schemas. The local ExtensionStore already emitted this event; without it the
  remote schema cache stayed stale for the lifetime of the server process.

- Include available LSP adapters in the project settings schema, matching the
  user settings schema (this was fixed for 'settings' in zed-industries#46766 but never
  applied to 'project_settings').
@cla-bot

cla-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @vancez on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.

@zed-community-bot zed-community-bot Bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Aug 8, 2026
@vancez

vancez commented Aug 8, 2026

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 8, 2026
@cla-bot

cla-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@dinocosta dinocosta added platform:remote Remote development, SSH and zed-remote-server area:settings Feedback for preferences, configuration, etc labels Aug 11, 2026
@SomeoneToIgnore SomeoneToIgnore self-assigned this Aug 18, 2026

@SomeoneToIgnore SomeoneToIgnore left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you, but we cannot change the dependencies of a fundamental crate that easy.

I think there's a way out, posted that and a few style notes.

Comment thread crates/extension_host/src/headless_host.rs Outdated
Comment thread crates/json_schema_store/src/json_schema_store.rs Outdated
Comment thread crates/workspace/Cargo.toml Outdated
- Add remote::OnRemoteClientCreated global callback invoked when a remote project is created, covering dev server, worktree picker and other connection paths
- Replace hand-written dedup with sort_unstable + dedup in settings schema handlers
- Remove redundant comments
@vancez
vancez requested a review from SomeoneToIgnore August 21, 2026 13:14

@SomeoneToIgnore SomeoneToIgnore left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you, this looks almost ready.

Main concern is that

Image

definitely not there yet, can we add one, otherwise it may regress in the future?

Comment thread crates/extension_host/src/headless_host.rs Outdated
Comment thread crates/extension_host/src/extension_host.rs
Comment thread crates/project/src/project.rs
- Emit RemoteClientEvent::Reconnected when a remote connection re-establishes, and re-sync extensions to that client since a restarted server process starts with an empty extension store
- Sync only the newly registered client in register_remote_client instead of re-syncing every registered connection
- Add a test covering the OnRemoteClientCreated callback invoked when a remote project is created

@SomeoneToIgnore SomeoneToIgnore left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still seeing a bunch of this in new subscriptions if you want to fix more along the way with the other fixes.

.await
.log_err();
})
.detach();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Before this PR, every sync went through ssh_registered_tx into the single loop task in ExtensionStore::new, so syncs to one server were serialized.

A detached task here is unordered relative to that loop, so a registration sync can race a debounce-driven update_remote_clients pass to the same client (window: opening a remote project within RELOAD_DEBOUNCE_DURATION of an extension index change).
Both would see the same missing_extensions, so the worst case per collision is a duplicate upload plus the losing install_extension uninstalling and reinstalling the extension the winner just loaded.
The detached future also captures the strong client handle across the whole upload, keeping the RemoteClient alive after the project is closed.

I think we do not need the spawn at all: make the channel carry a payload, e.g. UnboundedSender<Option<WeakEntity<RemoteClient>>>, where Some means "sync only this client, without the debounce" and None keeps the current debounced full pass for reconnects?
Then this function goes back to a synchronous push + send as before the PR, the loop task stays the sole sync owner, and we still avoid the K round-trips from the other comment.


cx.subscribe(&client, |this, _client, event, _cx| {
if matches!(event, RemoteClientEvent::Reconnected) {
this.ssh_registered_tx.unbounded_send(()).ok();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great, thank you for the reconnect fix: but this is also syncing all the clients now?
I think if we do the .detach() fix below that should go away due to Option<WeakEntity usage, but wanted to raise the awareness as something to be re-checked when fixed.

}

#[gpui::test]
async fn test_remote_project_creation_triggers_on_remote_client_created_callback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's a great start, thank you — do we want to cover the reconnect case too, as there's definitely some issues with that based on the other comments.

}
}

if extensions_changed {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If every load fails or all are missing (e.g. on the very initial load), we would notify for nothing?
Seems redundant to do so and we can check things better to exclude this case.

- Registering a client syncs only that client through the extension sync
  channel instead of a detached task, keeping the loop task the sole
  sync owner
- Reconnects still trigger a debounced pass over all clients
- Notify listeners only when extensions were actually loaded or removed
- Add tests for per-client sync on registration and re-sync on reconnect
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:settings Feedback for preferences, configuration, etc cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions platform:remote Remote development, SSH and zed-remote-server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants