@@ -363,9 +363,13 @@ impl RubyCrawler {
363363 // is the user's own machine state, so it is trusted like the
364364 // env var: no containment guard. Bundler's `Settings#path`
365365 // takes the FIRST tier that sets `path` or `path.system`, so a
366- // local or env setting shadows it entirely.
367- let shadowed = bundle_path_env. is_some_and ( |v| !v. is_empty ( ) )
368- || Self :: app_config_sets_path ( cwd, app_config_env) . await ;
366+ // local or env setting shadows it entirely — even an empty env
367+ // `BUNDLE_PATH`, which adds no root above but still stops
368+ // Bundler (`explicit_path` is `""`). An env
369+ // `BUNDLE_PATH__SYSTEM` already dropped `global_config`, see
370+ // [`global_path_config_unless_env_path_system`].
371+ let shadowed =
372+ bundle_path_env. is_some ( ) || Self :: app_config_sets_path ( cwd, app_config_env) . await ;
369373 if !shadowed {
370374 if let Some ( value) = read_global_config ( global_config, false )
371375 . await
@@ -1091,13 +1095,33 @@ pub(crate) fn bundler_global_config_file(
10911095}
10921096
10931097/// [`ambient_bundler_global_config_file`], or `None` under
1094- /// `BUNDLE_IGNORE_CONFIG` — for the install-root probe, which takes no
1095- /// `ignore_config` flag of its own.
1098+ /// `BUNDLE_IGNORE_CONFIG` or when the environment sets `path.system` — for
1099+ /// the install-root probe, which takes no `ignore_config` flag of its own.
10961100fn ambient_global_config_unless_ignored ( root : & Path ) -> Option < PathBuf > {
10971101 if bundler_ignores_config ( ) {
10981102 None
10991103 } else {
1100- ambient_bundler_global_config_file ( root)
1104+ global_path_config_unless_env_path_system (
1105+ ambient_bundler_global_config_file ( root) ,
1106+ std:: env:: var_os ( "BUNDLE_PATH__SYSTEM" ) . as_deref ( ) ,
1107+ )
1108+ }
1109+ }
1110+
1111+ /// The global config file for the install-root probe, or `None` when the
1112+ /// env tier sets `path.system`. Bundler's `Settings#path` stops at the
1113+ /// first tier that sets `path` OR `path.system`, and the env tier sits
1114+ /// above the global one, so any `BUNDLE_PATH__SYSTEM` value (even `"false"`
1115+ /// or empty) shadows a global `path` — the env `BUNDLE_PATH` half of that
1116+ /// rule is applied in [`RubyCrawler::discover_bundle_stores_with_env`].
1117+ fn global_path_config_unless_env_path_system (
1118+ global_config : Option < PathBuf > ,
1119+ path_system_env : Option < & OsStr > ,
1120+ ) -> Option < PathBuf > {
1121+ if path_system_env. is_some ( ) {
1122+ None
1123+ } else {
1124+ global_config
11011125 }
11021126}
11031127
@@ -2386,6 +2410,17 @@ mod tests {
23862410 . await
23872411 . stores ;
23882412 assert ! ( stores. is_empty( ) , "{stores:?}" ) ;
2413+ // Even empty: Bundler stops at the env tier (`explicit_path` `""`).
2414+ let stores = RubyCrawler :: discover_bundle_stores_with_env (
2415+ & root,
2416+ Some ( OsStr :: new ( "" ) ) ,
2417+ None ,
2418+ None ,
2419+ g,
2420+ )
2421+ . await
2422+ . stores ;
2423+ assert ! ( stores. is_empty( ) , "{stores:?}" ) ;
23892424
23902425 // So does a local `path.system` (bundler stops at the local tier).
23912426 std:: fs:: create_dir ( root. join ( ".bundle" ) ) . unwrap ( ) ;
@@ -2408,6 +2443,32 @@ mod tests {
24082443 assert ! ( stores. is_empty( ) , "{stores:?}" ) ;
24092444 }
24102445
2446+ /// An env `BUNDLE_PATH__SYSTEM` — any value, even `"false"` or empty —
2447+ /// shadows a global `path` like an env `BUNDLE_PATH` does: Bundler's
2448+ /// `Settings#path` stops at the env tier (checked against Bundler
2449+ /// 4.0.17: `explicit_path` is `nil` for each value).
2450+ #[ test]
2451+ fn env_path_system_shadows_the_global_config_path ( ) {
2452+ let global = Some ( PathBuf :: from ( "/home/u/.bundle/config" ) ) ;
2453+ for value in [ "true" , "false" ] {
2454+ assert_eq ! (
2455+ global_path_config_unless_env_path_system( global. clone( ) , Some ( OsStr :: new( value) ) ) ,
2456+ None ,
2457+ "BUNDLE_PATH__SYSTEM={value}"
2458+ ) ;
2459+ }
2460+ // Bundler reads an empty value as set too (`explicit_path` nil).
2461+ assert_eq ! (
2462+ global_path_config_unless_env_path_system( global. clone( ) , Some ( OsStr :: new( "" ) ) ) ,
2463+ None
2464+ ) ;
2465+ // Unset: the global file still applies.
2466+ assert_eq ! (
2467+ global_path_config_unless_env_path_system( global. clone( ) , None ) ,
2468+ global
2469+ ) ;
2470+ }
2471+
24112472 #[ tokio:: test]
24122473 async fn discovery_records_skipped_config_path ( ) {
24132474 let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
0 commit comments