@@ -697,6 +697,109 @@ def cleanup_temp_paths_from_registry() -> tuple[int, list[str]]:
697697 return 0 , []
698698
699699
700+ def refresh_path_from_registry () -> bool :
701+ """Refresh os.environ['PATH'] from Windows registry.
702+
703+ Reads both system and user PATH values from the Windows registry and
704+ updates os.environ['PATH'] with the combined value. This addresses
705+ the Windows PATH propagation bug where installations (e.g., winget)
706+ update the registry but running processes don't see the changes.
707+
708+ Registry sources:
709+ - System PATH: HKEY_LOCAL_MACHINE\\ SYSTEM\\ CurrentControlSet\\ Control\\ Session Manager\\ Environment
710+ - User PATH: HKEY_CURRENT_USER\\ Environment
711+
712+ Returns:
713+ True if PATH was successfully refreshed, False otherwise.
714+
715+ Note:
716+ - Only works on Windows (returns True on other platforms as no-op)
717+ - Handles REG_EXPAND_SZ values with environment variable expansion
718+ - Combines system PATH + user PATH (system first for security)
719+ - Logs info message when PATH is refreshed
720+ """
721+ if sys .platform == 'win32' :
722+ try :
723+
724+ def expand_env_vars (value : str ) -> str :
725+ """Expand environment variables like %USERPROFILE% in registry values."""
726+ # Use os.path.expandvars which handles %VAR% on Windows
727+ return os .path .expandvars (value )
728+
729+ system_path = ''
730+ user_path = ''
731+
732+ # Read system PATH from HKEY_LOCAL_MACHINE
733+ try :
734+ with winreg .OpenKey (
735+ winreg .HKEY_LOCAL_MACHINE ,
736+ r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment' ,
737+ 0 ,
738+ winreg .KEY_READ | winreg .KEY_WOW64_64KEY ,
739+ ) as key :
740+ raw_value , reg_type = winreg .QueryValueEx (key , 'Path' )
741+ if raw_value :
742+ # Expand environment variables if REG_EXPAND_SZ
743+ system_path = expand_env_vars (raw_value ) if reg_type == winreg .REG_EXPAND_SZ else raw_value
744+ except FileNotFoundError :
745+ # System PATH key doesn't exist (very unusual)
746+ pass
747+ except PermissionError :
748+ # May not have permission to read system PATH
749+ warning ('Permission denied reading system PATH from registry' )
750+ except OSError as e :
751+ warning (f'Failed to read system PATH from registry: { e } ' )
752+
753+ # Read user PATH from HKEY_CURRENT_USER
754+ try :
755+ with winreg .OpenKey (
756+ winreg .HKEY_CURRENT_USER ,
757+ r'Environment' ,
758+ 0 ,
759+ winreg .KEY_READ ,
760+ ) as key :
761+ raw_value , reg_type = winreg .QueryValueEx (key , 'Path' )
762+ if raw_value :
763+ # Expand environment variables if REG_EXPAND_SZ
764+ user_path = expand_env_vars (raw_value ) if reg_type == winreg .REG_EXPAND_SZ else raw_value
765+ except FileNotFoundError :
766+ # User PATH doesn't exist (possible on fresh systems)
767+ pass
768+ except PermissionError :
769+ warning ('Permission denied reading user PATH from registry' )
770+ except OSError as e :
771+ warning (f'Failed to read user PATH from registry: { e } ' )
772+
773+ # Combine paths: system PATH first, then user PATH
774+ # This matches Windows behavior where system PATH takes precedence
775+ if system_path and user_path :
776+ new_path = f'{ system_path } ;{ user_path } '
777+ elif system_path :
778+ new_path = system_path
779+ elif user_path :
780+ new_path = user_path
781+ else :
782+ # No PATH found in registry, keep current
783+ warning ('No PATH found in registry, keeping current os.environ PATH' )
784+ return False
785+
786+ # Update os.environ with the refreshed PATH
787+ old_path = os .environ .get ('PATH' , '' )
788+ if new_path != old_path :
789+ os .environ ['PATH' ] = new_path
790+ info ('Refreshed PATH from Windows registry' )
791+ return True
792+ # PATH unchanged, no need to log
793+ return True
794+
795+ except Exception as e :
796+ warning (f'Failed to refresh PATH from registry: { e } ' )
797+ return False
798+ else :
799+ # Non-Windows platforms: no-op, return True
800+ return True
801+
802+
700803def check_file_with_head (url : str , auth_headers : dict [str , str ] | None = None ) -> bool :
701804 """Check if file exists using HEAD request.
702805
@@ -2107,61 +2210,69 @@ def install_dependencies(dependencies: dict[str, list[str]] | None) -> bool:
21072210 # Collect dependencies: platform-specific first, then common
21082211 # This ensures platform runtimes (e.g., Node.js) are installed before
21092212 # common tools that depend on them (e.g., npm packages)
2110- deps_to_install : list [str ] = []
2213+ platform_deps_list : list [str ] = []
2214+ common_deps_list : list [str ] = []
21112215
2112- # Add platform-specific dependencies first (e.g., Node.js runtime)
2216+ # Collect platform-specific dependencies (e.g., Node.js runtime)
21132217 if current_platform_key :
21142218 platform_deps = dependencies .get (current_platform_key , [])
21152219 if platform_deps :
21162220 info (f'Found { len (platform_deps )} { current_platform_key } -specific dependencies' )
2117- deps_to_install . extend (platform_deps )
2221+ platform_deps_list = list (platform_deps )
21182222
2119- # Add common dependencies second (e.g., npm packages that need Node.js)
2223+ # Collect common dependencies (e.g., npm packages that need Node.js)
21202224 common_deps = dependencies .get ('common' , [])
21212225 if common_deps :
21222226 info (f'Found { len (common_deps )} common dependencies' )
2123- deps_to_install . extend (common_deps )
2227+ common_deps_list = list (common_deps )
21242228
2125- if not deps_to_install :
2229+ if not platform_deps_list and not common_deps_list :
21262230 info ('No dependencies to install for this platform' )
21272231 return True
21282232
2129- # Execute all collected dependencies
2130- for dep in deps_to_install :
2233+ # Helper function to execute a single dependency
2234+ def execute_dependency (dep : str ) -> bool :
2235+ """Execute a single dependency command. Returns True on success."""
21312236 info (f'Running: { dep } ' )
2132-
2133- # Parse the command
21342237 parts = dep .split ()
21352238
2136- # Handle platform-specific commands
21372239 if system == 'Windows' :
21382240 if parts [0 ] in ['winget' , 'npm' , 'pip' , 'pipx' ]:
21392241 result = run_command (parts , capture_output = False )
2140- elif parts [0 ] == 'uv' and parts [1 ] == 'tool' and parts [2 ] == 'install' :
2141- # For uv tool install, add --force to update if already installed
2242+ elif parts [0 ] == 'uv' and len (parts ) >= 3 and parts [1 ] == 'tool' and parts [2 ] == 'install' :
21422243 parts_with_force = parts [:3 ] + ['--force' ] + parts [3 :]
21432244 result = run_command (parts_with_force , capture_output = False )
21442245 else :
2145- # Try PowerShell for other commands
21462246 result = run_command (['powershell' , '-Command' , dep ], capture_output = False )
21472247 else :
2148- # Unix-like systems
21492248 if parts [0 ] == 'uv' and len (parts ) >= 3 and parts [1 ] == 'tool' and parts [2 ] == 'install' :
2150- # For uv tool install, add --force to update if already installed
21512249 dep_with_force = dep .replace ('uv tool install' , 'uv tool install --force' )
21522250 result = run_command (['bash' , '-c' , dep_with_force ], capture_output = False )
21532251 else :
2154- # Execute command as-is (expand tilde paths before execution)
21552252 expanded_dep = expand_tildes_in_command (dep )
21562253 result = run_command (['bash' , '-c' , expanded_dep ], capture_output = False )
21572254
21582255 if result .returncode != 0 :
21592256 error (f'Failed to install dependency: { dep } ' )
2160- # Check if it failed due to admin rights on Windows
21612257 if system == 'Windows' and not is_admin () and 'winget' in dep and '--scope machine' in dep :
21622258 warning ('This may have failed due to lack of admin rights' )
21632259 info ('Try: 1) Run as administrator, or 2) Use --scope user instead' )
21642260 warning ('Continuing with other dependencies...' )
2261+ return False
2262+ return True
2263+
2264+ # Phase 1: Execute platform-specific dependencies
2265+ for dep in platform_deps_list :
2266+ execute_dependency (dep )
2267+
2268+ # Phase 2: Refresh PATH from registry on Windows
2269+ # This picks up any PATH changes from platform-specific installations (e.g., Node.js)
2270+ if system == 'Windows' and platform_deps_list :
2271+ refresh_path_from_registry ()
2272+
2273+ # Phase 3: Execute common dependencies
2274+ for dep in common_deps_list :
2275+ execute_dependency (dep )
21652276
21662277 return True
21672278
0 commit comments