From f3db0da499afe6c4f8bb5b6b5986f633de961228 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 23 Aug 2026 18:04:00 +0900 Subject: [PATCH 1/3] Disable transport-level retries on Bundler connections Gem::Request already resends requests on stale connections and Bundler::Retry retries failed requests, so the Gem::Net::HTTP retry layer (max_retries defaults to 1) only multiplied the number of attempts, doubling the worst-case wait on a host that times out. Co-Authored-By: Claude Fable 5 --- lib/bundler/fetcher/connection_pools.rb | 4 ++++ lib/bundler/man/bundle-config.1 | 2 +- lib/bundler/man/bundle-config.1.ronn | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/bundler/fetcher/connection_pools.rb b/lib/bundler/fetcher/connection_pools.rb index a2e72af74b80..91005e4d1768 100644 --- a/lib/bundler/fetcher/connection_pools.rb +++ b/lib/bundler/fetcher/connection_pools.rb @@ -90,6 +90,10 @@ def build_connection(uri, proxy_uri) # :nodoc: configure_ssl(connection) if uri.scheme == "https" connection.open_timeout = @timeout connection.read_timeout = @timeout + # Stale connections are already resent by Gem::Request#perform_request, + # and Bundler::Retry retries whole requests, so the Gem::Net::HTTP + # level retry would only multiply the time spent on a timing out host. + connection.max_retries = 0 connection.start connection end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 8cf56c9a1306..8e2942caaaea 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -179,7 +179,7 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated .IP "\(bu" 4 \fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. .IP "\(bu" 4 -\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests, applied to each request individually\. A network operation retried by Bundler may wait a multiple of this value in total\. Defaults to \fB10\fR\. .IP "\(bu" 4 \fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. .IP "\(bu" 4 diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 27fd5e040b5a..2141acfdc9ea 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -293,7 +293,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `system_bindir` (`BUNDLE_SYSTEM_BINDIR`): The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. * `timeout` (`BUNDLE_TIMEOUT`): - The seconds allowed before timing out for network requests. Defaults to `10`. + The seconds allowed before timing out for network requests, applied to each + request individually. A network operation retried by Bundler may wait a + multiple of this value in total. Defaults to `10`. * `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`): Require passing `--all` to `bundle update` when everything should be updated, and disallow passing no options to `bundle update`. From 1de5bfc11cd7b60b5d8dfd704d08b3fccdac167e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 23 Aug 2026 18:04:21 +0900 Subject: [PATCH 2/3] Restore NO_PROXY wildcard support in Bundler proxy detection net-http-persistent treated NO_PROXY="*" as a request to bypass the proxy for every host, but Gem::URI::Generic.use_proxy? parses "*" as a literal hostname that never matches, so the wildcard stopped disabling proxies after the switch to Gem::Request. Co-Authored-By: Claude Fable 5 --- lib/bundler/fetcher/connection_pools.rb | 3 +++ spec/bundler/fetcher_spec.rb | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/lib/bundler/fetcher/connection_pools.rb b/lib/bundler/fetcher/connection_pools.rb index 91005e4d1768..09514b610cb3 100644 --- a/lib/bundler/fetcher/connection_pools.rb +++ b/lib/bundler/fetcher/connection_pools.rb @@ -72,6 +72,9 @@ def proxy_for(uri) env_proxy_for(uri) end return unless proxy + # NO_PROXY="*" bypasses the proxy for every host, which + # Gem::URI::Generic.use_proxy? does not understand. + return if no_proxy_env.strip == "*" return unless Gem::URI::Generic.use_proxy?(uri.hostname, nil, uri.port, no_proxy_env) proxy end diff --git a/spec/bundler/fetcher_spec.rb b/spec/bundler/fetcher_spec.rb index 87a49ea6fc78..8bac5548b924 100644 --- a/spec/bundler/fetcher_spec.rb +++ b/spec/bundler/fetcher_spec.rb @@ -82,6 +82,11 @@ expect(fetcher.http_proxy).to be_nil end end + it "bypass proxy for every host when no_proxy is '*'" do + with_env_vars("HTTP_PROXY" => "http://proxy-example5.com", "NO_PROXY" => "*") do + expect(fetcher.http_proxy).to be_nil + end + end end def configured_connection From 8c04a9d88ea63e12af49ed017e01021116529c77 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 23 Aug 2026 18:04:46 +0900 Subject: [PATCH 3/3] Document Bundler proxy semantics in bundle-config The switch from net-http-persistent to Gem::Request (#9786) aligned Bundler proxy handling with RubyGems: https_proxy now takes precedence for https sources, proxy credential variables follow the scheme, :http_proxy: :no_proxy in .gemrc now also disables environment proxies, and NO_PROXY matching is strict about dot boundaries. None of this was documented anywhere, so describe the current semantics in the bundle-config man page. Co-Authored-By: Claude Fable 5 --- lib/bundler/man/bundle-config.1 | 6 ++++++ lib/bundler/man/bundle-config.1.ronn | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 8e2942caaaea..88f7f67b4bcc 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -330,6 +330,12 @@ Any \fB\.\fR characters in a host name are mapped to a double underscore (\fB__\ .IP "" 0 .P This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\. +.SH "PROXY SUPPORT" +Bundler reads the proxy for a gem source from the \fB:http_proxy\fR key in the RubyGems configuration file (\fB~/\.gemrc\fR), falling back to the environment\. For an \fBhttps\fR source, \fBhttps_proxy\fR (or \fBHTTPS_PROXY\fR) takes precedence over \fBhttp_proxy\fR (or \fBHTTP_PROXY\fR), which is only used when no https\-specific proxy is set\. Proxy credentials that are not embedded in the proxy URL are read from \fBHTTP_PROXY_USER\fR and \fBHTTP_PROXY_PASS\fR, or from \fBHTTPS_PROXY_USER\fR and \fBHTTPS_PROXY_PASS\fR when the https proxy is used\. +.P +Setting \fB:http_proxy: :no_proxy\fR in the RubyGems configuration file disables proxies entirely, including any proxy environment variables\. To keep using a proxy from the environment, remove that line\. To configure a proxy in the file, replace \fB:no_proxy\fR with an explicit proxy URL\. +.P +The \fBNO_PROXY\fR (or \fBno_proxy\fR) environment variable lists hosts that are reached directly, bypassing the proxy\. An entry matches the host itself and its subdomains, so \fBNO_PROXY=example\.com\fR bypasses both \fBexample\.com\fR and \fBsub\.example\.com\fR, but not \fBmyexample\.com\fR\. An entry starting with a dot matches only subdomains\. A value of \fB*\fR bypasses the proxy for every host\. .SH "CONFIGURE BUNDLER DIRECTORIES" Bundler's home, cache and plugin directories and config file can be configured through environment variables\. The default location for Bundler's home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values .IP "" 4 diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 2141acfdc9ea..6e9fedd109af 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -454,6 +454,27 @@ This means that if you have a gem server named `my.gem-host.com`, you'll need to use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it through ENV. +## PROXY SUPPORT + +Bundler reads the proxy for a gem source from the `:http_proxy` key in the +RubyGems configuration file (`~/.gemrc`), falling back to the environment. +For an `https` source, `https_proxy` (or `HTTPS_PROXY`) takes precedence over +`http_proxy` (or `HTTP_PROXY`), which is only used when no https-specific +proxy is set. Proxy credentials that are not embedded in the proxy URL are +read from `HTTP_PROXY_USER` and `HTTP_PROXY_PASS`, or from `HTTPS_PROXY_USER` +and `HTTPS_PROXY_PASS` when the https proxy is used. + +Setting `:http_proxy: :no_proxy` in the RubyGems configuration file disables +proxies entirely, including any proxy environment variables. To keep using a +proxy from the environment, remove that line. To configure a proxy in the +file, replace `:no_proxy` with an explicit proxy URL. + +The `NO_PROXY` (or `no_proxy`) environment variable lists hosts that are +reached directly, bypassing the proxy. An entry matches the host itself and +its subdomains, so `NO_PROXY=example.com` bypasses both `example.com` and +`sub.example.com`, but not `myexample.com`. An entry starting with a dot +matches only subdomains. A value of `*` bypasses the proxy for every host. + ## CONFIGURE BUNDLER DIRECTORIES Bundler's home, cache and plugin directories and config file can be configured