@@ -6,6 +6,7 @@ use crate::ipc_framed::ErrorPayload;
66use crate :: ipc_framed:: ErrorStage ;
77use crate :: ipc_framed:: SpawnRequest ;
88use crate :: resolved_permissions:: ResolvedWindowsSandboxPermissions ;
9+ use crate :: runner_client:: RunnerLogonError ;
910use crate :: runner_client:: RunnerStartupError ;
1011use codex_protocol:: models:: PermissionProfile ;
1112use codex_utils_absolute_path:: AbsolutePathBuf ;
@@ -16,7 +17,10 @@ use std::cell::RefCell;
1617use std:: collections:: HashMap ;
1718use std:: path:: Path ;
1819use std:: path:: PathBuf ;
20+ use std:: process:: Command ;
21+ use tempfile:: tempdir;
1922use windows_sys:: Win32 :: Foundation :: ERROR_NO_SUCH_LOGON_SESSION ;
23+ use windows_sys:: Win32 :: Foundation :: ERROR_SERVICE_ALREADY_RUNNING ;
2024
2125#[ derive( Debug , Clone , PartialEq , Eq ) ]
2226struct SpawnObservation {
@@ -43,8 +47,7 @@ struct RefreshObservation {
4347 proxy_settings_mode : WindowsSandboxProxySettingsMode ,
4448}
4549
46- #[ test]
47- fn retry_uses_original_unified_exec_request_and_stops_after_second_failure ( ) {
50+ fn runner_transport_request ( ) -> RunnerTransportRequest {
4851 let workspace_root = AbsolutePathBuf :: from_absolute_path ( PathBuf :: from ( r"C:\workspace" ) )
4952 . expect ( "absolute workspace root" ) ;
5053 let permission_profile = PermissionProfile :: workspace_write ( ) ;
@@ -61,16 +64,16 @@ fn retry_uses_original_unified_exec_request_and_stops_after_second_failure() {
6164 "http://localhost:1234" . to_string ( ) ,
6265 ) ,
6366 ] ) ;
64- let request = RunnerTransportRequest {
65- permissions : permissions . clone ( ) ,
67+ RunnerTransportRequest {
68+ permissions,
6669 codex_home : PathBuf :: from ( r"C:\Users\codex" ) ,
6770 cwd : PathBuf :: from ( r"C:\workspace" ) ,
6871 env_map : env_map. clone ( ) ,
6972 logs_base_dir : Some ( PathBuf :: from ( r"C:\Users\codex\.sandbox" ) ) ,
7073 spawn_request : SpawnRequest {
7174 command : vec ! [ "pwsh.exe" . to_string( ) , "-NoProfile" . to_string( ) ] ,
7275 cwd : PathBuf :: from ( r"C:\workspace" ) ,
73- env : env_map. clone ( ) ,
76+ env : env_map,
7477 permission_profile,
7578 workspace_roots : vec ! [ workspace_root] ,
7679 codex_home : PathBuf :: from ( r"C:\Users\codex\.sandbox" ) ,
@@ -89,13 +92,18 @@ fn retry_uses_original_unified_exec_request_and_stops_after_second_failure() {
8992 deny_write_paths_override : vec ! [ PathBuf :: from( r"C:\workspace\.codex" ) ] ,
9093 proxy_enforced : true ,
9194 proxy_settings_mode : WindowsSandboxProxySettingsMode :: Preserve ,
92- } ;
95+ }
96+ }
97+
98+ #[ test]
99+ fn retry_uses_original_unified_exec_request_and_stops_after_second_failure ( ) {
100+ let request = runner_transport_request ( ) ;
93101 let expected_spawn_request =
94102 serde_json:: to_value ( & request. spawn_request ) . expect ( "serialize spawn request" ) ;
95103 let expected_refresh = RefreshObservation {
96- permissions,
104+ permissions : request . permissions . clone ( ) ,
97105 cwd : request. cwd . clone ( ) ,
98- env_map,
106+ env_map : request . env_map . clone ( ) ,
99107 codex_home : request. codex_home . clone ( ) ,
100108 read_roots_override : request. read_roots_override . clone ( ) ,
101109 read_roots_include_platform_defaults : true ,
@@ -191,3 +199,62 @@ fn retry_uses_original_unified_exec_request_and_stops_after_second_failure() {
191199 ) ;
192200 assert_eq ! ( vec![ expected_refresh] , * refresh_observations. borrow( ) ) ;
193201}
202+
203+ #[ test]
204+ fn unified_exec_logon_1056_retries_original_request_and_runs_command_once ( ) {
205+ let workspace = tempdir ( ) . expect ( "temporary workspace" ) ;
206+ let mut request = runner_transport_request ( ) ;
207+ request. cwd = workspace. path ( ) . to_path_buf ( ) ;
208+ request. spawn_request . cwd = request. cwd . clone ( ) ;
209+ request. spawn_request . command = vec ! [
210+ std:: env:: var( "COMSPEC" ) . expect( "Windows command interpreter" ) ,
211+ "/d" . into( ) ,
212+ "/c" . into( ) ,
213+ "echo launched>>command-launches.txt" . into( ) ,
214+ ] ;
215+ let expected = SpawnObservation {
216+ codex_home : request. codex_home . clone ( ) ,
217+ cwd : request. cwd . clone ( ) ,
218+ username : "sandbox" . into ( ) ,
219+ password : "unchanged-test-password" . into ( ) ,
220+ logs_base_dir : request. logs_base_dir . clone ( ) ,
221+ spawn_request : serde_json:: to_value ( & request. spawn_request ) . expect ( "serialize request" ) ,
222+ } ;
223+ let mut attempts = Vec :: new ( ) ;
224+ let status = spawn_runner_transport_with_retry (
225+ SandboxCreds {
226+ username : expected. username . clone ( ) ,
227+ password : expected. password . clone ( ) ,
228+ } ,
229+ & request,
230+ |codex_home, cwd, creds, logs_base_dir, spawn_request| {
231+ attempts. push ( SpawnObservation {
232+ codex_home : codex_home. to_path_buf ( ) ,
233+ cwd : cwd. to_path_buf ( ) ,
234+ username : creds. username . clone ( ) ,
235+ password : creds. password . clone ( ) ,
236+ logs_base_dir : logs_base_dir. map ( Path :: to_path_buf) ,
237+ spawn_request : serde_json:: to_value ( & spawn_request) . expect ( "serialize request" ) ,
238+ } ) ;
239+ if attempts. len ( ) == 1 {
240+ return Err ( RunnerLogonError {
241+ code : ERROR_SERVICE_ALREADY_RUNNING ,
242+ }
243+ . into ( ) ) ;
244+ }
245+ Ok ( Command :: new ( & spawn_request. command [ 0 ] )
246+ . args ( & spawn_request. command [ 1 ..] )
247+ . current_dir ( & spawn_request. cwd )
248+ . status ( ) ?)
249+ } ,
250+ |_, _, _, _, _, _, _, _, _, _, _| panic ! ( "1056 must never refresh credentials" ) ,
251+ )
252+ . expect ( "unified exec should recover from runner logon 1056" ) ;
253+ assert ! ( status. success( ) ) ;
254+ assert_eq ! ( attempts, vec![ expected. clone( ) , expected] ) ;
255+ assert_eq ! (
256+ std:: fs:: read_to_string( workspace. path( ) . join( "command-launches.txt" ) )
257+ . expect( "read command launch marker" ) ,
258+ "launched\r \n "
259+ ) ;
260+ }
0 commit comments