Skip to content

Fix LSP client on Windows: stream reading and server request handling#4

Open
ronti1 wants to merge 1 commit intoHYMMA:masterfrom
ronti1:fix/lsp-client-windows-and-server-requests
Open

Fix LSP client on Windows: stream reading and server request handling#4
ronti1 wants to merge 1 commit intoHYMMA:masterfrom
ronti1:fix/lsp-client-windows-and-server-requests

Conversation

@ronti1
Copy link
Copy Markdown

@ronti1 ronti1 commented Mar 23, 2026

Fix LSP client on Windows: stream reading and server request handling

Problem

The LSP client fails on Windows with two issues:

  1. Stream reading hangs: ReadContentLengthAsync reads from Process.StandardOutput.BaseStream byte-by-byte, which silently fails on Windows pipes. The .NET Process class wraps stdout in a StreamReader with internal buffering — reading the underlying BaseStream bypasses this buffer, causing ReadAsync to never receive data even though csharp-ls writes correctly.

  2. Server requests dropped: ProcessMessage treats all messages with an id field as responses to client requests. However, csharp-ls sends server-to-client requests (e.g., client/registerCapability, workspace/configuration) that have both id and method fields. These are silently dropped, causing csharp-ls to block waiting for responses and never process subsequent requests.

Fix

Stream reading — replaced BaseStream byte-by-byte reading with StreamReader.ReadLineAsync() for headers and StreamReader.ReadAsync(Memory<char>) for body content. Set StandardOutputEncoding = new UTF8Encoding(false) to ensure consistent encoding.

Server requests — updated ProcessMessage to distinguish three JSON-RPC message types:

  • id + method → server request → auto-respond with {result: null}
  • id only → client response → dispatch to pending TCS
  • method only → notification → handle/log

Added SendServerResponseAsync that builds JSON manually to avoid WhenWritingNull stripping the result: null field.

Testing

Verified on Windows with csharp-ls 0.22.0 (.NET 10):

  • set_workspace → loads solution successfully
  • csharp_symbols → returns document symbols
  • csharp_hover, csharp_definition, csharp_references → all working
  • All 9 MCP tools tested and passing

The LSP client's ProcessMessage treated all messages with an 'id' field
as responses to client requests. However, LSP servers also send requests
TO the client (e.g. client/registerCapability, workspace/configuration)
which have both 'id' AND 'method' fields. These were silently dropped,
causing the server to block waiting for a response and never process
subsequent client requests like textDocument/documentSymbol.

Changes:
- Distinguish server-sent requests (id+method) from responses (id only)
  and notifications (method only) in ProcessMessage
- Add SendServerResponseAsync to reply with {result:null} to server
  requests, using manual JSON to avoid WhenWritingNull stripping the
  result field
- Use StreamReader for header reading (ReadLineAsync) instead of
  byte-by-byte BaseStream reading, matching the previous ReadLoopAsync
  fix
- Add debug logging for notifications and unknown response IDs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants