mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
fix(models): refresh a stale ChatGPT plan roster at launch
Only three call sites refreshed the 24 h plan roster (~/.codewhale/catalog/chatgpt-plan-<sha>.json): PKCE sign-in, the owned login flow and an explicit `models --update`. A signed-in account that never re-authenticated kept whatever it first fetched, so `codewhale models --provider openai-codex` could report models that the account no longer offers and never learn about new ones. The configured-provider catalog sweep skips OpenaiCodex because provider_owns_live_catalog excludes it, so nothing at launch repaired the roster. spawn_refresh_if_stale runs the same fetch the manual path uses whenever the roster is Missing, Stale or Invalid, from the launch block that already refreshes the other three catalog layers. A Fresh roster costs one blocking freshness probe and no request. load_snapshot also returned an empty roster once past TTL, which is how a stale cache turned into "no ChatGPT models at all" instead of a stale list. Rows now survive staleness, matching Models.dev and provider catalog behavior: freshness stays Stale, the preferred default stays Fresh-only, and the new test asserts ids ["old"] with no preferred model. Verified: ./scripts/dev-cargo.sh check -p codewhale-tui -p codewhale-config (exit 0); test -p codewhale-tui codex_model_cache:: (5 passed). The stale test was updated because the old empty-roster assertion encoded the bug. Not verified here: a live launch against an expired cache.
This commit is contained in:
1 parent
853841506b
commit
c869044358
2 files changed
+41
-5
No files matched your search
@@ -204,10 +204,14 @@ fn load_snapshot(path: &Path, now: DateTime<Utc>) -> CodexModelRoster {
|
||||
);
|
||||
}
|
||||
if age > MODEL_CACHE_MAX_AGE {
|
||||
return CodexModelRoster::fallback(
|
||||
CodexModelCacheFreshness::Stale,
|
||||
Some(snapshot.fetched_at),
|
||||
);
|
||||
return CodexModelRoster {
|
||||
models: snapshot.models,
|
||||
freshness: CodexModelCacheFreshness::Stale,
|
||||
fetched_at: Some(snapshot.fetched_at),
|
||||
observed_at: None,
|
||||
source: "chatgpt_plan_api",
|
||||
observation_persisted: true,
|
||||
};
|
||||
}
|
||||
CodexModelRoster {
|
||||
models: snapshot.models,
|
||||
@@ -296,6 +300,36 @@ pub(crate) async fn update_from_chatgpt(config: &Config) -> Result<CodexModelRos
|
||||
.map_err(|_| "cache_write_failed")?
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_refresh_if_stale(config: &Config) {
|
||||
let Ok(handle) = tokio::runtime::Handle::try_current() else {
|
||||
return;
|
||||
};
|
||||
let config = config.clone();
|
||||
handle.spawn(async move {
|
||||
let probe = config.clone();
|
||||
let Ok(freshness) =
|
||||
tokio::task::spawn_blocking(move || model_roster_for(&probe).freshness).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if freshness == CodexModelCacheFreshness::Fresh {
|
||||
return;
|
||||
}
|
||||
match update_from_chatgpt(&config).await {
|
||||
Ok(roster) => tracing::debug!(
|
||||
target: "codex_model_cache",
|
||||
model_count = roster.models.len(),
|
||||
"ChatGPT plan roster refreshed"
|
||||
),
|
||||
Err(err) => tracing::debug!(
|
||||
target: "codex_model_cache",
|
||||
error = %err,
|
||||
"ChatGPT plan roster refresh skipped"
|
||||
),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn read_cache_bytes(path: &Path) -> Result<Vec<u8>, CodexModelCacheFreshness> {
|
||||
let path_metadata = match std::fs::symlink_metadata(path) {
|
||||
Ok(metadata) => metadata,
|
||||
@@ -502,7 +536,8 @@ mod tests {
|
||||
);
|
||||
let stale = load_snapshot(&path, now);
|
||||
assert_eq!(stale.freshness, CodexModelCacheFreshness::Stale);
|
||||
assert!(stale.models.is_empty());
|
||||
assert_eq!(stale.model_ids(), ["old"]);
|
||||
assert_eq!(stale.preferred_model_id(), None);
|
||||
save(
|
||||
&path,
|
||||
now + MAX_FUTURE_CLOCK_SKEW + Duration::seconds(1),
|
||||
|
||||
@@ -13675,6 +13675,7 @@ async fn run_interactive_prepared(
|
||||
// not currently on are probed too; otherwise a relay's roster stays empty
|
||||
// until the user switches to it.
|
||||
crate::client::CodewhaleClient::spawn_configured_provider_catalog_refresh(config);
|
||||
crate::codex_model_cache::spawn_refresh_if_stale(config);
|
||||
|
||||
// Boot janitors — snapshot prune (7-day default), spillover prune
|
||||
// (#422), and managed-session cleanup (v0.8.44) — are best-effort disk
|
||||
|
||||
Reference in new issue
Block a user