From a327992c86d9f64b252a2f19ac1c3a42fca55261 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 12 Jul 2026 13:38:02 +0000 Subject: [PATCH] fix(lsp): sent initial workspace configuration Sent workspace/didChangeConfiguration after initialized so servers waiting for client configuration finish startup before semantic requests. Added a fake-LSP regression covering notification order and settings payload. Fixes #5276 --- packages/coding-agent/CHANGELOG.md | 4 ++ packages/coding-agent/src/lsp/client.ts | 8 ++- .../test/tools/lsp-regressions.test.ts | 60 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 737235f6f63..ee6a09ca1d4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Pyright LSP semantic requests hanging during startup by sending the initial workspace configuration notification after `initialized`. ([#5276](https://github.com/can1357/oh-my-pi/issues/5276)) + ## [16.4.7] - 2026-07-12 ### Added diff --git a/packages/coding-agent/src/lsp/client.ts b/packages/coding-agent/src/lsp/client.ts index 649c4f800d0..e511f7cc879 100644 --- a/packages/coding-agent/src/lsp/client.ts +++ b/packages/coding-agent/src/lsp/client.ts @@ -750,8 +750,14 @@ export async function getOrCreateClient( client.serverCapabilities = initResult.capabilities as LspClient["serverCapabilities"]; - // Send initialized notification + // Finish the initialize handshake before publishing the client as ready. await sendNotification(client, "initialized", {}, signal); + await sendNotification( + client, + "workspace/didChangeConfiguration", + { settings: config.settings ?? {} }, + signal, + ); client.status = "ready"; // Publish only after init succeeds: pre-init clients are reachable diff --git a/packages/coding-agent/test/tools/lsp-regressions.test.ts b/packages/coding-agent/test/tools/lsp-regressions.test.ts index 872fb054084..1a83e11a41a 100644 --- a/packages/coding-agent/test/tools/lsp-regressions.test.ts +++ b/packages/coding-agent/test/tools/lsp-regressions.test.ts @@ -366,6 +366,66 @@ describe("lsp regressions", () => { } }); + it("sends initial workspace configuration after initialized before semantic requests (#5276)", async () => { + const tempDir = TempDir.createSync("@omp-lsp-initial-config-"); + try { + const server = installFakeLsp((message, srv) => { + if (message.method === "initialize") { + srv.send({ jsonrpc: "2.0", id: message.id, result: { capabilities: { hoverProvider: true } } }); + } else if (message.method === "textDocument/hover") { + srv.send({ jsonrpc: "2.0", id: message.id, result: { contents: "configured hover" } }); + } else if (message.method === "shutdown") { + srv.send({ jsonrpc: "2.0", id: message.id, result: null }); + } else if (message.method === "exit") { + srv.exit(0); + } + }); + const settings = { + "typescript-language-server": { + preferences: { + includePackageJsonAutoImports: "on", + importModuleSpecifierPreference: "non-relative", + }, + inlayHints: { + includeInlayEnumMemberValueHints: true, + includeInlayParameterNameHints: "all", + }, + }, + }; + const config: ServerConfig = { + command: "fake-lsp", + fileTypes: ["ts"], + rootMarkers: [], + settings, + }; + + const client = await lspClient.getOrCreateClient(config, tempDir.path(), 1_000); + const result = await lspClient.sendRequest( + client, + "textDocument/hover", + { + textDocument: { uri: fileToUri(path.join(tempDir.path(), "src", "configured.ts")) }, + position: { line: 0, character: 0 }, + }, + undefined, + 50, + ); + + expect(result).toEqual({ contents: "configured hover" }); + const methods = server.received.map(message => message.method); + expect(methods.slice(0, 4)).toEqual([ + "initialize", + "initialized", + "workspace/didChangeConfiguration", + "textDocument/hover", + ]); + expect(server.received[2].params).toEqual({ settings: config.settings }); + } finally { + await lspClient.shutdownAll(); + tempDir.removeSync(); + } + }); + it("accepts dynamic capability registration before semantic requests", async () => { const tempDir = TempDir.createSync("@omp-lsp-dynamic-registration-"); try {