From 17a79c229d7fb88a7fd4e1f7bbabbc106a0fb8a7 Mon Sep 17 00:00:00 2001
From: Matt Odille <matt.odille@gmail.com>
Date: Thu, 5 Feb 2026 00:24:43 -0500
Subject: [PATCH] Add rbs to composed bundle update lists

Without rbs in the update list, conservative mode can't resolve
ruby-lsp 0.18+ for projects that don't have rbs locked, causing
unexpected downgrades to 0.11.2.

Fixes https://github.com/Shopify/ruby-lsp/issues/3937
---
 lib/ruby_lsp/setup_bundler.rb | 20 +++++++++++---------
 test/setup_bundler_test.rb    |  4 ++--
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/lib/ruby_lsp/setup_bundler.rb b/lib/ruby_lsp/setup_bundler.rb
index 47b86a485b..5f714e5dea 100644
--- a/lib/ruby_lsp/setup_bundler.rb
+++ b/lib/ruby_lsp/setup_bundler.rb
@@ -331,7 +331,7 @@ def bundle_install
     def update(env)
       # Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
       # source control changes
-      gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
+      gems = ["ruby-lsp", "debug", "prism", "rbs"].reject { |dep| @dependencies[dep] }
       gems << "ruby-lsp-rails" if @rails_app && !@dependencies["ruby-lsp-rails"]
 
       Bundler::CLI::Update.new({ conservative: true }, gems).run
@@ -343,14 +343,15 @@ def update(env)
 
     #: (Hash[String, String] env) -> Hash[String, String]
     def run_bundle_install_through_command(env)
-      # If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
-      # to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just created
-      # and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
-      # install` for the first time to generate the Gemfile.lock with them included or else Bundler will complain that
-      # they're missing. We can only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems
+      # If `ruby-lsp`, `debug` and `rbs` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we
+      # shouldn't try to upgrade them or else we'll produce undesired source control changes. If the composed bundle was
+      # just created and any of `ruby-lsp`, `ruby-lsp-rails`, `debug` or `rbs` weren't a part of the Gemfile, then we
+      # need to run `bundle install` for the first time to generate the Gemfile.lock with them included or else Bundler
+      # will complain that they're missing. We can only update if the custom `.ruby-lsp/Gemfile.lock` already exists and
+      # includes all gems
 
       # When not updating, we run `(bundle check || bundle install)`
-      # When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
+      # When updating, we run `((bundle check && bundle update ruby-lsp debug rbs) || bundle install)`
       bundler_path = File.join(Gem.default_bindir, "bundle")
       base_command = (!Gem.win_platform? && File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
 
@@ -361,12 +362,13 @@ def run_bundle_install_through_command(env)
       command = +"(#{base_command} check"
 
       if should_bundle_update?
-        # If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
-        # version
+        # If any of `ruby-lsp`, `ruby-lsp-rails`, `debug` or `rbs` are not in the Gemfile, try to update them to the
+        # latest version
         command.prepend("(")
         command << " && #{base_command} update "
         command << "ruby-lsp " unless @dependencies["ruby-lsp"]
         command << "debug " unless @dependencies["debug"]
+        command << "rbs " unless @dependencies["rbs"]
         command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
         command.delete_suffix!(" ")
         command << ")"
diff --git a/test/setup_bundler_test.rb b/test/setup_bundler_test.rb
index 265dfdcf6f..ed9ade976f 100644
--- a/test/setup_bundler_test.rb
+++ b/test/setup_bundler_test.rb
@@ -176,7 +176,7 @@ def test_does_not_copy_gemfile_lock_when_not_modified
         Bundler.with_unbundled_env do
           stub_bundle_with_env(
             bundle_env(dir, ".ruby-lsp/Gemfile"),
-            /((bundle _[\d\.]+_ check && bundle _[\d\.]+_ update ruby-lsp debug) || bundle _[\d\.]+_ install) 1>&2/,
+            /((bundle _[\d\.]+_ check && bundle _[\d\.]+_ update ruby-lsp debug rbs) || bundle _[\d\.]+_ install) 1>&2/,
           )
 
           FileUtils.expects(:cp).never
@@ -732,7 +732,7 @@ def test_invoke_cli_calls_bundler_directly_for_update
           require "bundler/cli/update"
           Bundler::CLI::Update.expects(:new).with(
             { conservative: true },
-            ["ruby-lsp", "debug", "prism"],
+            ["ruby-lsp", "debug", "prism", "rbs"],
           ).returns(mock_update)
 
           FileUtils.touch(File.join(dir, ".ruby-lsp", "needs_update"))
