diff --git a/codex-rs/models-manager/src/cache.rs b/codex-rs/models-manager/src/cache.rs index 70fc1e01e3..702cc714dc 100644 --- a/codex-rs/models-manager/src/cache.rs +++ b/codex-rs/models-manager/src/cache.rs @@ -193,7 +193,7 @@ pub(crate) struct ModelsCache { } impl ModelsCache { - /// Returns `true` when the cache entry has not exceeded the configured TTL. + /// Returns `true` when the cache entry is not future-dated and has not exceeded the TTL. fn is_fresh(&self, ttl: Duration) -> bool { if ttl.is_zero() { return false; @@ -202,6 +202,6 @@ impl ModelsCache { return false; }; let age = Utc::now().signed_duration_since(self.fetched_at); - age <= ttl_duration + age >= chrono::Duration::zero() && age <= ttl_duration } } diff --git a/codex-rs/models-manager/src/manager_tests.rs b/codex-rs/models-manager/src/manager_tests.rs index 03656f9cae..4ed60e7d78 100644 --- a/codex-rs/models-manager/src/manager_tests.rs +++ b/codex-rs/models-manager/src/manager_tests.rs @@ -1267,6 +1267,39 @@ async fn refresh_available_models_refetches_when_cache_stale() { ); } +#[tokio::test] +async fn refresh_available_models_refetches_when_cache_timestamp_is_in_the_future() { + let initial_models = vec![remote_model("future", "Future", /*priority*/ 1)]; + let codex_home = tempdir().expect("temp dir"); + let updated_models = vec![remote_model("refetched", "Refetched", /*priority*/ 9)]; + let endpoint = TestModelsEndpoint::new(vec![initial_models.clone(), updated_models.clone()]); + let manager = openai_manager_for_tests(codex_home.path().to_path_buf(), endpoint.clone()); + + manager + .refresh_available_models(RefreshStrategy::OnlineIfUncached) + .await + .expect("initial refresh succeeds"); + + manager + .cache_manager + .manipulate_cache_for_test(|fetched_at| { + *fetched_at = Utc::now() + chrono::Duration::hours(1); + }) + .await + .expect("cache manipulation succeeds"); + + manager + .refresh_available_models(RefreshStrategy::OnlineIfUncached) + .await + .expect("second refresh succeeds"); + assert_models_contain(&manager.get_remote_models().await, &updated_models); + assert_eq!( + endpoint.fetch_count(), + 2, + "future-dated cache should fetch models again" + ); +} + #[tokio::test] async fn refresh_available_models_refetches_when_version_mismatch() { let initial_models = vec![remote_model("old", "Old", /*priority*/ 1)];