@@ -12,6 +12,7 @@ use tauri::{WebviewUrl, WebviewWindowBuilder};
1212use tokio:: io:: { AsyncBufReadExt , AsyncWriteExt , BufReader } ;
1313use tokio:: process:: { Child , ChildStdin , Command } ;
1414use tokio:: sync:: { Mutex , oneshot} ;
15+ use tokio:: time:: { timeout, Duration } ;
1516use uuid:: Uuid ;
1617
1718#[ derive( Debug , Serialize , Deserialize , Clone ) ]
@@ -216,11 +217,24 @@ fn build_codex_command(entry: &WorkspaceEntry) -> Command {
216217 . filter ( |value| !value. trim ( ) . is_empty ( ) )
217218 . unwrap_or_else ( || "codex" . into ( ) ) ;
218219 let mut command = Command :: new ( bin) ;
220+ // Cargo sets DYLD_* vars in dev runs on macOS so that the current binary can
221+ // locate its dependent dylibs. Those variables leak into child processes and
222+ // can break unrelated binaries (like `codex`) by forcing them to load the
223+ // wrong libraries. Strip them for the app-server child process.
224+ command. env_remove ( "DYLD_LIBRARY_PATH" ) ;
225+ command. env_remove ( "DYLD_FALLBACK_LIBRARY_PATH" ) ;
226+ command. env_remove ( "DYLD_FRAMEWORK_PATH" ) ;
227+ command. env_remove ( "DYLD_FALLBACK_FRAMEWORK_PATH" ) ;
228+ command. env_remove ( "DYLD_INSERT_LIBRARIES" ) ;
219229 if default_bin {
220230 let mut paths: Vec < String > = env:: var ( "PATH" )
221231 . unwrap_or_default ( )
222232 . split ( ':' )
223233 . filter ( |value| !value. is_empty ( ) )
234+ // `npm run` prepends a bunch of `node_modules/.bin` folders, which can
235+ // accidentally shadow the real `codex` install with an unrelated
236+ // script. We want the system `codex` binary here.
237+ . filter ( |value| !value. contains ( "node_modules/.bin" ) )
224238 . map ( |value| value. to_string ( ) )
225239 . collect ( ) ;
226240 let mut extras = vec ! [
@@ -254,6 +268,12 @@ async fn spawn_workspace_session(
254268 entry : WorkspaceEntry ,
255269 app_handle : AppHandle ,
256270) -> Result < Arc < WorkspaceSession > , String > {
271+ if cfg ! ( debug_assertions) {
272+ eprintln ! (
273+ "[codex-monitor] spawning codex app-server workspace_id={} path={}" ,
274+ entry. id, entry. path
275+ ) ;
276+ }
257277 let mut command = build_codex_command ( & entry) ;
258278 command. arg ( "app-server" ) ;
259279 command. stdin ( std:: process:: Stdio :: piped ( ) ) ;
@@ -323,6 +343,26 @@ async fn spawn_workspace_session(
323343 let _ = app_handle_clone. emit ( "app-server-event" , payload) ;
324344 }
325345 }
346+
347+ if cfg ! ( debug_assertions) {
348+ eprintln ! ( "[codex-monitor] app-server stdout closed workspace_id={workspace_id}" ) ;
349+ }
350+ if cfg ! ( debug_assertions) {
351+ let status = {
352+ let mut child = session_clone. child . lock ( ) . await ;
353+ child
354+ . try_wait ( )
355+ . ok ( )
356+ . flatten ( )
357+ . map ( |value| value. to_string ( ) )
358+ } ;
359+ if let Some ( status) = status {
360+ eprintln ! (
361+ "[codex-monitor] app-server exit status workspace_id={workspace_id} {status}"
362+ ) ;
363+ }
364+ }
365+ session_clone. pending . lock ( ) . await . clear ( ) ;
326366 } ) ;
327367
328368 let workspace_id = entry. id . clone ( ) ;
@@ -333,6 +373,9 @@ async fn spawn_workspace_session(
333373 if line. trim ( ) . is_empty ( ) {
334374 continue ;
335375 }
376+ if cfg ! ( debug_assertions) {
377+ eprintln ! ( "[codex-monitor] app-server stderr workspace_id={workspace_id} {line}" ) ;
378+ }
336379 let payload = AppServerEvent {
337380 workspace_id : workspace_id. clone ( ) ,
338381 message : json ! ( {
@@ -351,7 +394,22 @@ async fn spawn_workspace_session(
351394 "version" : "0.1.0"
352395 }
353396 } ) ;
354- session. send_request ( "initialize" , init_params) . await ?;
397+ match timeout ( Duration :: from_secs ( 5 ) , session. send_request ( "initialize" , init_params) ) . await {
398+ Ok ( result) => {
399+ result?;
400+ }
401+ Err ( _) => {
402+ if cfg ! ( debug_assertions) {
403+ eprintln ! (
404+ "[codex-monitor] app-server initialize timed out workspace_id={}" ,
405+ entry. id
406+ ) ;
407+ }
408+ let mut child = session. child . lock ( ) . await ;
409+ let _ = child. kill ( ) . await ;
410+ return Err ( "codex app-server initialize timed out" . to_string ( ) ) ;
411+ }
412+ }
355413 session. send_notification ( "initialized" , None ) . await ?;
356414
357415 let payload = AppServerEvent {
@@ -368,6 +426,9 @@ async fn spawn_workspace_session(
368426
369427#[ tauri:: command]
370428async fn list_workspaces ( state : State < ' _ , AppState > ) -> Result < Vec < WorkspaceInfo > , String > {
429+ if cfg ! ( debug_assertions) {
430+ eprintln ! ( "[codex-monitor] list_workspaces" ) ;
431+ }
371432 let workspaces = state. workspaces . lock ( ) . await ;
372433 let sessions = state. sessions . lock ( ) . await ;
373434 let mut result = Vec :: new ( ) ;
@@ -392,6 +453,9 @@ async fn add_workspace(
392453 state : State < ' _ , AppState > ,
393454 app : AppHandle ,
394455) -> Result < WorkspaceInfo , String > {
456+ if cfg ! ( debug_assertions) {
457+ eprintln ! ( "[codex-monitor] add_workspace path={path}" ) ;
458+ }
395459 let name = PathBuf :: from ( & path)
396460 . file_name ( )
397461 . and_then ( |s| s. to_str ( ) )
@@ -405,7 +469,14 @@ async fn add_workspace(
405469 settings : WorkspaceSettings :: default ( ) ,
406470 } ;
407471
408- let session = spawn_workspace_session ( entry. clone ( ) , app) . await ?;
472+ let session = spawn_workspace_session ( entry. clone ( ) , app. clone ( ) )
473+ . await
474+ . map_err ( |error| {
475+ if cfg ! ( debug_assertions) {
476+ eprintln ! ( "[codex-monitor] add_workspace spawn failed: {error}" ) ;
477+ }
478+ error
479+ } ) ?;
409480 {
410481 let mut workspaces = state. workspaces . lock ( ) . await ;
411482 workspaces. insert ( entry. id . clone ( ) , entry. clone ( ) ) ;
0 commit comments