From 3ff0a1e772bb5382fbb11d066c1148e334bfc439 Mon Sep 17 00:00:00 2001 From: Igor Froehner Date: Mon, 30 Mar 2026 23:30:21 -0300 Subject: [PATCH 1/2] Return MethodNotFound (-32601) for unrecognized LSP requests The process_message case statement had no else clause, so requests for unsupported methods were silently dropped. This caused clients to hang indefinitely waiting for a response. The LSP spec requires servers to respond with error code -32601 for unsupported methods. Fixes #3981 --- lib/ruby_lsp/server.rb | 6 ++++++ test/server_test.rb | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index 170c755da..30819246b 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -120,6 +120,12 @@ def process_message(message) @global_state.synchronize { @cancelled_requests << message[:params][:id] } when nil process_response(message) if message[:result] + else + send_message(Error.new( + id: message[:id], + code: -32601, + message: "Method not found: #{message[:method]}", + )) if message[:id] end rescue DelegateRequestError send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST")) diff --git a/test/server_test.rb b/test/server_test.rb index f38c20fba..2b126a51f 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -1726,6 +1726,18 @@ def test_launch_bundle_compose_forwards_argv_to_launcher ARGV.replace(original_argv) end + def test_unrecognized_request_returns_method_not_found + @server.process_message({ + id: 1, + method: "textDocument/nonExistentMethod", + params: {}, + }) + + error = find_message(RubyLsp::Error) + assert_equal(-32601, error.code) + assert_equal("Method not found: textDocument/nonExistentMethod", error.message) + end + private def run_initialize_server_with_setup_error(error) From 8a8da0c1a9dc2cdddf32f39b2e1d3fa8676b40fa Mon Sep 17 00:00:00 2001 From: Igor Froehner Date: Tue, 31 Mar 2026 16:27:21 -0300 Subject: [PATCH 2/2] Use a constant error code --- lib/ruby_lsp/server.rb | 14 +++++++++----- test/server_test.rb | 7 ++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index 30819246b..6a1956d2c 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -121,11 +121,15 @@ def process_message(message) when nil process_response(message) if message[:result] else - send_message(Error.new( - id: message[:id], - code: -32601, - message: "Method not found: #{message[:method]}", - )) if message[:id] + id = message[:id] + + if id + send_message(Error.new( + id: id, + code: Constant::ErrorCodes::METHOD_NOT_FOUND, + message: "Method not found: #{message[:method]}", + )) + end end rescue DelegateRequestError send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST")) diff --git a/test/server_test.rb b/test/server_test.rb index 2b126a51f..12b6a0e5a 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -1727,15 +1727,16 @@ def test_launch_bundle_compose_forwards_argv_to_launcher end def test_unrecognized_request_returns_method_not_found + non_existent_method = "textDocument/nonExistentMethod" @server.process_message({ id: 1, - method: "textDocument/nonExistentMethod", + method: non_existent_method, params: {}, }) error = find_message(RubyLsp::Error) - assert_equal(-32601, error.code) - assert_equal("Method not found: textDocument/nonExistentMethod", error.message) + assert_equal(Constant::ErrorCodes::METHOD_NOT_FOUND, error.code) + assert_equal("Method not found: #{non_existent_method}", error.message) end private