@@ -6,6 +6,7 @@ use std::io::ErrorKind;
66use std:: path:: { Path , PathBuf } ;
77use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
88use std:: sync:: Arc ;
9+ use std:: sync:: OnceLock ;
910use std:: time:: Duration ;
1011
1112use tokio:: io:: { AsyncBufReadExt , AsyncWriteExt , BufReader } ;
@@ -357,6 +358,55 @@ fn resolve_windows_command_path(
357358 None
358359}
359360
361+ #[ cfg( windows) ]
362+ #[ derive( Debug , Clone , Hash , PartialEq , Eq ) ]
363+ struct WindowsCommandCacheKey {
364+ command : String ,
365+ path_env : Option < String > ,
366+ cwd : Option < String > ,
367+ }
368+
369+ #[ cfg( windows) ]
370+ static WINDOWS_COMMAND_PATH_CACHE : OnceLock <
371+ std:: sync:: Mutex < HashMap < WindowsCommandCacheKey , PathBuf > > ,
372+ > = OnceLock :: new ( ) ;
373+
374+ #[ cfg( windows) ]
375+ fn resolve_windows_command_path_cached (
376+ command : & str ,
377+ path_env : Option < & OsString > ,
378+ cwd_for_resolution : Option < & Path > ,
379+ ) -> Option < PathBuf > {
380+ let trimmed = command. trim ( ) ;
381+ if trimmed. is_empty ( ) {
382+ return None ;
383+ }
384+
385+ let cwd_key = if trimmed. contains ( '\\' ) || trimmed. contains ( '/' ) {
386+ cwd_for_resolution. map ( |cwd| cwd. to_string_lossy ( ) . to_string ( ) )
387+ } else {
388+ None
389+ } ;
390+ let key = WindowsCommandCacheKey {
391+ command : trimmed. to_string ( ) ,
392+ path_env : path_env. map ( |value| value. to_string_lossy ( ) . to_string ( ) ) ,
393+ cwd : cwd_key,
394+ } ;
395+
396+ let cache = WINDOWS_COMMAND_PATH_CACHE . get_or_init ( || std:: sync:: Mutex :: new ( HashMap :: new ( ) ) ) ;
397+ if let Ok ( guard) = cache. lock ( ) {
398+ if let Some ( hit) = guard. get ( & key) {
399+ return Some ( hit. clone ( ) ) ;
400+ }
401+ }
402+
403+ let resolved = resolve_windows_command_path ( trimmed, path_env, cwd_for_resolution) ?;
404+ if let Ok ( mut guard) = cache. lock ( ) {
405+ guard. insert ( key, resolved. clone ( ) ) ;
406+ }
407+ Some ( resolved)
408+ }
409+
360410pub ( crate ) fn build_codex_command_with_bin (
361411 codex_bin : Option < String > ,
362412 cwd_for_resolution : Option < & Path > ,
@@ -378,7 +428,7 @@ pub(crate) fn build_codex_command_with_bin(
378428 ) ;
379429
380430 #[ cfg( windows) ]
381- let bin = resolve_windows_command_path ( raw_bin, path_env. as_ref ( ) , cwd_for_resolution)
431+ let bin = resolve_windows_command_path_cached ( raw_bin, path_env. as_ref ( ) , cwd_for_resolution)
382432 . map ( |path| path. to_string_lossy ( ) . to_string ( ) )
383433 . unwrap_or_else ( || raw_bin. to_string ( ) ) ;
384434
@@ -506,8 +556,17 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
506556 . clone ( )
507557 . filter ( |value| !value. trim ( ) . is_empty ( ) )
508558 . or ( default_codex_bin) ;
559+ let codex_bin_for_check = codex_bin. clone ( ) ;
509560 let workspace_dir = Path :: new ( & entry. path ) ;
510- let _ = check_codex_installation ( codex_bin. clone ( ) , Some ( workspace_dir) ) . await ?;
561+ if !workspace_dir. is_dir ( ) {
562+ return Err ( format ! (
563+ "Workspace folder does not exist: `{}`" ,
564+ workspace_dir. display( )
565+ ) ) ;
566+ }
567+ if !cfg ! ( windows) {
568+ let _ = check_codex_installation ( codex_bin_for_check. clone ( ) , Some ( workspace_dir) ) . await ?;
569+ }
511570
512571 let mut command = build_codex_command_with_bin ( codex_bin, Some ( workspace_dir) ) ;
513572 apply_codex_args ( & mut command, codex_args. as_deref ( ) ) ?;
@@ -520,7 +579,19 @@ pub(crate) async fn spawn_workspace_session<E: EventSink>(
520579 command. stdout ( std:: process:: Stdio :: piped ( ) ) ;
521580 command. stderr ( std:: process:: Stdio :: piped ( ) ) ;
522581
523- let mut child = command. spawn ( ) . map_err ( |e| e. to_string ( ) ) ?;
582+ let mut child = match command. spawn ( ) {
583+ Ok ( child) => child,
584+ Err ( err) => {
585+ if err. kind ( ) == ErrorKind :: NotFound && workspace_dir. is_dir ( ) {
586+ if let Err ( error) =
587+ check_codex_installation ( codex_bin_for_check. clone ( ) , Some ( workspace_dir) ) . await
588+ {
589+ return Err ( error) ;
590+ }
591+ }
592+ return Err ( err. to_string ( ) ) ;
593+ }
594+ } ;
524595 let stdin = child. stdin . take ( ) . ok_or ( "missing stdin" ) ?;
525596 let stdout = child. stdout . take ( ) . ok_or ( "missing stdout" ) ?;
526597 let stderr = child. stderr . take ( ) . ok_or ( "missing stderr" ) ?;
0 commit comments