Author: Danny Milosavljevic Date: 2026-07-11 License: ASL2.0 Subject: Disable app-server network model refresh in tests. Codex 0.144.1 starts a background app-server model refresh worker at startup. That worker calls list_models with RefreshStrategy::Online, so prewriting models_cache.json does not keep it off the network. Tests that spawn a codex-app-server child must not depend on remote network behavior or start unrelated background network tasks. The app-server integration suite already passes the hidden --disable-plugin-startup-tasks-for-tests flag to suppress startup side work in spawned child servers. Extend that test-only flag to skip the model refresh worker as well. Production startup keeps the worker enabled. diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 32a58dba2..7935f3a91 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -427,18 +427,26 @@ pub enum PluginStartupTasks { Skip, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModelsRefreshStartup { + Start, + Skip, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AppServerRuntimeOptions { pub plugin_startup_tasks: PluginStartupTasks, + pub models_refresh_startup: ModelsRefreshStartup, pub remote_control_startup_mode: RemoteControlStartupMode, pub install_shutdown_signal_handler: bool, } impl Default for AppServerRuntimeOptions { fn default() -> Self { Self { plugin_startup_tasks: PluginStartupTasks::Start, + models_refresh_startup: ModelsRefreshStartup::Start, remote_control_startup_mode: RemoteControlStartupMode::ResolvePersisted, install_shutdown_signal_handler: true, } } @@ -905,6 +912,7 @@ pub async fn run_main_with_transport_options( rpc_transport: analytics_rpc_transport(&transport), remote_control_handle: Some(remote_control_handle.clone()), plugin_startup_tasks: runtime_options.plugin_startup_tasks, + models_refresh_startup: runtime_options.models_refresh_startup, })); let mut thread_created_rx = processor.thread_created_receiver(); let mut running_turn_count_rx = processor.subscribe_running_assistant_turn_count(); diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index f81c65c2a..1eb834771 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -450,6 +450,7 @@ impl InProcessAppServer { rpc_transport: AppServerRpcTransport::InProcess, remote_control_handle: None, plugin_startup_tasks: crate::PluginStartupTasks::Start, + models_refresh_startup: crate::ModelsRefreshStartup::Start, })); let mut thread_created_rx = processor.thread_created_receiver(); let session = Arc::new(ConnectionSessionState::new()); diff --git a/codex-rs/app-server/src/main.rs b/codex-rs/app-server/src/main.rs index 874d20521..3104a2d03 100644 --- a/codex-rs/app-server/src/main.rs +++ b/codex-rs/app-server/src/main.rs @@ -2,6 +2,7 @@ use clap::Parser; use codex_app_server::AppServerRuntimeOptions; use codex_app_server::AppServerTransport; use codex_app_server::AppServerWebsocketAuthArgs; +use codex_app_server::ModelsRefreshStartup; use codex_app_server::PluginStartupTasks; use codex_app_server::run_main_with_transport_options; use codex_arg0::Arg0DispatchPaths; @@ -84,6 +85,7 @@ fn main() -> anyhow::Result<()> { #[cfg(debug_assertions)] if disable_plugin_startup_tasks_for_tests { runtime_options.plugin_startup_tasks = PluginStartupTasks::Skip; + runtime_options.models_refresh_startup = ModelsRefreshStartup::Skip; } runtime_options.remote_control_startup_mode = match (remote_control, remote_control_disabled) { diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 7d86f872a..b2da65a2a 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -101,7 +101,7 @@ fn decode_request_payload(payload: JsonRpcMessagePayload) -> Result, - models_refresh_worker: ModelsRefreshWorker, + models_refresh_worker: Option, skills_watcher: Arc, account_processor: AccountRequestProcessor, apps_processor: AppsRequestProcessor, @@ -219,6 +219,7 @@ pub(crate) struct MessageProcessorArgs { pub(crate) rpc_transport: AppServerRpcTransport, pub(crate) remote_control_handle: Option, pub(crate) plugin_startup_tasks: crate::PluginStartupTasks, + pub(crate) models_refresh_startup: crate::ModelsRefreshStartup, } impl MessageProcessor { @@ -242,6 +243,7 @@ impl MessageProcessor { rpc_transport, remote_control_handle, plugin_startup_tasks, + models_refresh_startup, } = args; let thread_state_manager = ThreadStateManager::new(); // The thread store is intentionally process-scoped. Config reloads can @@ -299,8 +301,13 @@ impl MessageProcessor { ) }); let models_manager = thread_manager.get_models_manager(); - let models_refresh_worker = - crate::models_refresh_worker::spawn(&models_manager, config.http_client_factory()); + let models_refresh_worker = match models_refresh_startup { + crate::ModelsRefreshStartup::Start => Some(crate::models_refresh_worker::spawn( + &models_manager, + config.http_client_factory(), + )), + crate::ModelsRefreshStartup::Skip => None, + }; thread_manager .plugins_manager() .set_analytics_events_client(analytics_events_client.clone()); @@ -499,7 +506,9 @@ impl MessageProcessor { pub(crate) fn clear_runtime_references(&self) { self.account_processor.clear_external_auth(); self.apps_processor.shutdown(); - self.models_refresh_worker.shutdown(); + if let Some(models_refresh_worker) = &self.models_refresh_worker { + models_refresh_worker.shutdown(); + } self.skills_watcher.shutdown(); } @@ -676,7 +685,9 @@ impl MessageProcessor { } pub(crate) async fn drain_background_tasks(&self) { - self.models_refresh_worker.shutdown(); + if let Some(models_refresh_worker) = &self.models_refresh_worker { + models_refresh_worker.shutdown(); + } self.thread_processor.drain_background_tasks().await; } diff --git a/codex-rs/app-server/src/message_processor_tracing_tests.rs b/codex-rs/app-server/src/message_processor_tracing_tests.rs index d839bd6f0..797813e35 100644 --- a/codex-rs/app-server/src/message_processor_tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor_tracing_tests.rs @@ -267,6 +267,7 @@ fn make_processor() -> (Arc, OutgoingMessageReceiver) { rpc_transport: AppServerRpcTransport::Stdio, remote_control_handle: None, plugin_startup_tasks: crate::PluginStartupTasks::Start, + models_refresh_startup: crate::ModelsRefreshStartup::Start, })); (processor, outgoing_rx) } diff --git a/codex-rs/app-server/tests/suite/v2/remote_control.rs b/codex-rs/app-server/tests/suite/v2/remote_control.rs index f85688ddc..66ab20e88 100644 --- a/codex-rs/app-server/tests/suite/v2/remote_control.rs +++ b/codex-rs/app-server/tests/suite/v2/remote_control.rs @@ -240,6 +240,7 @@ async fn explicit_remote_control_startup_fails_when_disabled_by_requirements() - AppServerWebsocketAuthSettings::default(), AppServerRuntimeOptions { plugin_startup_tasks: PluginStartupTasks::Skip, + models_refresh_startup: codex_app_server::ModelsRefreshStartup::Skip, remote_control_startup_mode: RemoteControlStartupMode::EnabledEphemeral, install_shutdown_signal_handler: false, },