From a4132d75234e652c9b80ef55b0b1f0bd3dcacff6 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 13 Dec 2025 21:02:56 -0800 Subject: [PATCH] error --- codex-rs/core/src/codex.rs | 19 +++++++- codex-rs/core/tests/suite/remote_models.rs | 50 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 126138861f..5cfc3d4bf6 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -253,10 +253,16 @@ impl Codex { let exec_policy = Arc::new(RwLock::new(exec_policy)); let config = Arc::new(config); + let mut remote_models_error: Option = None; if config.features.enabled(Feature::RemoteModels) && let Err(err) = models_manager.refresh_available_models(&config).await { error!("failed to refresh available models: {err:?}"); + remote_models_error = Some(EventMsg::Error(error_event_with_update_nudge( + format!("failed to refresh available models: {err:?}"), + None, + compute_is_up_to_date(&config.codex_home), + ))); } let model = models_manager.get_model(&config.model, &config).await; let is_up_to_date = compute_is_up_to_date(&config.codex_home); @@ -299,13 +305,24 @@ impl Codex { let conversation_id = session.conversation_id; // This task will run until Op::Shutdown is received. - tokio::spawn(submission_loop(session, config, rx_sub)); + tokio::spawn(submission_loop(session.clone(), config, rx_sub)); let codex = Codex { next_id: AtomicU64::new(0), tx_sub, rx_event, }; + if let Some(remote_models_error) = remote_models_error { + let event = Event { + id: session + .next_internal_sub_id + .fetch_add(1, Ordering::SeqCst) + .to_string(), + msg: remote_models_error, + }; + session.send_event_raw(event).await; + } + Ok(CodexSpawnOk { codex, conversation_id, diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index bf01196c78..1f5a2cb249 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -47,7 +47,11 @@ use tokio::time::Duration; use tokio::time::Instant; use tokio::time::sleep; use wiremock::BodyPrintLimit; +use wiremock::Mock; use wiremock::MockServer; +use wiremock::ResponseTemplate; +use wiremock::matchers::method; +use wiremock::matchers::path_regex; const REMOTE_MODEL_SLUG: &str = "codex-test"; @@ -298,6 +302,52 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn remote_models_invalid_payload_emits_error() -> Result<()> { + skip_if_no_network!(Ok(())); + skip_if_sandbox!(Ok(())); + + let server = MockServer::builder() + .body_print_limit(BodyPrintLimit::Limited(80_000)) + .start() + .await; + + Mock::given(method("GET")) + .and(path_regex(".*/models$")) + .respond_with( + ResponseTemplate::new(200) + .insert_header("content-type", "application/json") + .set_body_json(json!({ + "models": "invalid", + "etag": "etag", + })), + ) + .up_to_n_times(1) + .mount(&server) + .await; + + let RemoteModelsHarness { codex, .. } = build_remote_models_harness(&server, |config| { + config.features.enable(Feature::RemoteModels); + config.model = Some("gpt-5.1".to_string()); + }) + .await?; + + let error_event = wait_for_event(&codex, |msg| matches!(msg, EventMsg::Error(_))).await; + let EventMsg::Error(error_event) = error_event else { + unreachable!(); + }; + + assert!( + error_event + .message + .contains("failed to refresh available models"), + "unexpected error message: {}", + error_event.message + ); + + Ok(()) +} + async fn wait_for_model_available( manager: &Arc, slug: &str,