@@ -238,6 +238,7 @@ use crate::turn_diff_tracker::TurnDiffTracker;
238238use crate :: unified_exec:: UnifiedExecProcessManager ;
239239use crate :: util:: backoff;
240240use crate :: windows_sandbox:: WindowsSandboxLevelExt ;
241+ use crate :: zsh_exec_bridge:: ZshExecBridge ;
241242use codex_async_utils:: OrCancelExt ;
242243use codex_otel:: OtelManager ;
243244use codex_otel:: TelemetryAuthMode ;
@@ -1190,7 +1191,22 @@ impl Session {
11901191 config. active_profile . clone ( ) ,
11911192 ) ;
11921193
1193- let mut default_shell = shell:: default_user_shell ( ) ;
1194+ let use_zsh_fork_shell = config. features . enabled ( Feature :: ShellZshFork ) ;
1195+ let mut default_shell = if use_zsh_fork_shell {
1196+ let zsh_path = config. zsh_path . as_ref ( ) . ok_or_else ( || {
1197+ anyhow:: anyhow!(
1198+ "zsh fork feature enabled, but `zsh_path` is not configured; set `zsh_path` in config.toml"
1199+ )
1200+ } ) ?;
1201+ shell:: get_shell ( shell:: ShellType :: Zsh , Some ( zsh_path) ) . ok_or_else ( || {
1202+ anyhow:: anyhow!(
1203+ "zsh fork feature enabled, but zsh_path `{}` is not usable; set `zsh_path` to a valid zsh executable" ,
1204+ zsh_path. display( )
1205+ )
1206+ } ) ?
1207+ } else {
1208+ shell:: default_user_shell ( )
1209+ } ;
11941210 // Create the mutable state for the Session.
11951211 let shell_snapshot_tx = if config. features . enabled ( Feature :: ShellSnapshot ) {
11961212 ShellSnapshot :: start_snapshotting (
@@ -1261,10 +1277,17 @@ impl Session {
12611277 ( None , None )
12621278 } ;
12631279
1280+ let zsh_exec_bridge =
1281+ ZshExecBridge :: new ( config. zsh_path . clone ( ) , config. codex_home . clone ( ) ) ;
1282+ zsh_exec_bridge
1283+ . initialize_for_session ( & conversation_id. to_string ( ) )
1284+ . await ;
1285+
12641286 let services = SessionServices {
12651287 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
12661288 mcp_startup_cancellation_token : Mutex :: new ( CancellationToken :: new ( ) ) ,
12671289 unified_exec_manager : UnifiedExecProcessManager :: default ( ) ,
1290+ zsh_exec_bridge,
12681291 analytics_events_client : AnalyticsEventsClient :: new (
12691292 Arc :: clone ( & config) ,
12701293 Arc :: clone ( & auth_manager) ,
@@ -4049,6 +4072,7 @@ mod handlers {
40494072 . unified_exec_manager
40504073 . terminate_all_processes ( )
40514074 . await ;
4075+ sess. services . zsh_exec_bridge . shutdown ( ) . await ;
40524076 info ! ( "Shutting down Codex instance" ) ;
40534077 let history = sess. clone_history ( ) . await ;
40544078 let turn_count = history
@@ -7281,6 +7305,81 @@ mod tests {
72817305 }
72827306 }
72837307
7308+ #[ tokio:: test]
7309+ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path ( ) {
7310+ let codex_home = tempfile:: tempdir ( ) . expect ( "create temp dir" ) ;
7311+ let mut config = build_test_config ( codex_home. path ( ) ) . await ;
7312+ config. features . enable ( Feature :: ShellZshFork ) ;
7313+ config. zsh_path = None ;
7314+ let config = Arc :: new ( config) ;
7315+
7316+ let auth_manager =
7317+ AuthManager :: from_auth_for_testing ( CodexAuth :: from_api_key ( "Test API Key" ) ) ;
7318+ let models_manager = Arc :: new ( ModelsManager :: new (
7319+ config. codex_home . clone ( ) ,
7320+ auth_manager. clone ( ) ,
7321+ ) ) ;
7322+ let model = ModelsManager :: get_model_offline_for_tests ( config. model . as_deref ( ) ) ;
7323+ let model_info =
7324+ ModelsManager :: construct_model_info_offline_for_tests ( model. as_str ( ) , & config) ;
7325+ let collaboration_mode = CollaborationMode {
7326+ mode : ModeKind :: Default ,
7327+ settings : Settings {
7328+ model,
7329+ reasoning_effort : config. model_reasoning_effort ,
7330+ developer_instructions : None ,
7331+ } ,
7332+ } ;
7333+ let session_configuration = SessionConfiguration {
7334+ provider : config. model_provider . clone ( ) ,
7335+ collaboration_mode,
7336+ model_reasoning_summary : config. model_reasoning_summary ,
7337+ developer_instructions : config. developer_instructions . clone ( ) ,
7338+ user_instructions : config. user_instructions . clone ( ) ,
7339+ personality : config. personality ,
7340+ base_instructions : config
7341+ . base_instructions
7342+ . clone ( )
7343+ . unwrap_or_else ( || model_info. get_model_instructions ( config. personality ) ) ,
7344+ compact_prompt : config. compact_prompt . clone ( ) ,
7345+ approval_policy : config. permissions . approval_policy . clone ( ) ,
7346+ sandbox_policy : config. permissions . sandbox_policy . clone ( ) ,
7347+ windows_sandbox_level : WindowsSandboxLevel :: from_config ( & config) ,
7348+ cwd : config. cwd . clone ( ) ,
7349+ codex_home : config. codex_home . clone ( ) ,
7350+ thread_name : None ,
7351+ original_config_do_not_use : Arc :: clone ( & config) ,
7352+ session_source : SessionSource :: Exec ,
7353+ dynamic_tools : Vec :: new ( ) ,
7354+ persist_extended_history : false ,
7355+ } ;
7356+
7357+ let ( tx_event, _rx_event) = async_channel:: unbounded ( ) ;
7358+ let ( agent_status_tx, _agent_status_rx) = watch:: channel ( AgentStatus :: PendingInit ) ;
7359+ let result = Session :: new (
7360+ session_configuration,
7361+ Arc :: clone ( & config) ,
7362+ auth_manager,
7363+ models_manager,
7364+ ExecPolicyManager :: default ( ) ,
7365+ tx_event,
7366+ agent_status_tx,
7367+ InitialHistory :: New ,
7368+ SessionSource :: Exec ,
7369+ Arc :: new ( SkillsManager :: new ( config. codex_home . clone ( ) ) ) ,
7370+ Arc :: new ( FileWatcher :: noop ( ) ) ,
7371+ AgentControl :: default ( ) ,
7372+ )
7373+ . await ;
7374+
7375+ let err = match result {
7376+ Ok ( _) => panic ! ( "expected startup to fail" ) ,
7377+ Err ( err) => err,
7378+ } ;
7379+ let msg = format ! ( "{err:#}" ) ;
7380+ assert ! ( msg. contains( "zsh fork feature enabled, but `zsh_path` is not configured" ) ) ;
7381+ }
7382+
72847383 // todo: use online model info
72857384 pub ( crate ) async fn make_session_and_context ( ) -> ( Session , TurnContext ) {
72867385 let ( tx_event, _rx_event) = async_channel:: unbounded ( ) ;
@@ -7354,6 +7453,7 @@ mod tests {
73547453 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
73557454 mcp_startup_cancellation_token : Mutex :: new ( CancellationToken :: new ( ) ) ,
73567455 unified_exec_manager : UnifiedExecProcessManager :: default ( ) ,
7456+ zsh_exec_bridge : ZshExecBridge :: default ( ) ,
73577457 analytics_events_client : AnalyticsEventsClient :: new (
73587458 Arc :: clone ( & config) ,
73597459 Arc :: clone ( & auth_manager) ,
@@ -7502,6 +7602,7 @@ mod tests {
75027602 mcp_connection_manager : Arc :: new ( RwLock :: new ( McpConnectionManager :: default ( ) ) ) ,
75037603 mcp_startup_cancellation_token : Mutex :: new ( CancellationToken :: new ( ) ) ,
75047604 unified_exec_manager : UnifiedExecProcessManager :: default ( ) ,
7605+ zsh_exec_bridge : ZshExecBridge :: default ( ) ,
75057606 analytics_events_client : AnalyticsEventsClient :: new (
75067607 Arc :: clone ( & config) ,
75077608 Arc :: clone ( & auth_manager) ,
0 commit comments