BigPizzaV3/CodexPlusPlus · warning · anyhow::Error
ad list unavailable
Error message
ad list unavailable
What it means
fetch_ad_list_from_urls() (crates/codex-plus-core/src/ads.rs:229) iterates the candidate URL list, keeping the last per-URL error in last_error; after the loop it returns Err(last_error.unwrap_or_else(|| anyhow!("ad list unavailable"))). The unwrap_or_else branch only executes when the loop body never ran, i.e. the urls slice is empty — with a non-empty list a real network/HTTP/JSON error is propagated instead. So this exact message means zero ad-list URLs were attempted, not 'network down'.
Source
Thrown at crates/codex-plus-core/src/ads.rs:229
let cache_bust = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.unwrap_or_default();
let mut last_error = None;
for url in urls {
let url = cache_busted_ad_url(url.as_ref(), cache_bust);
let result = async {
let response = client.get(url).send().await?.error_for_status()?;
let payload = response.json::<Value>().await?;
Ok::<_, anyhow::Error>(normalize_ad_payload(payload))
}
.await;
match result {
Ok(payload) => return Ok(payload),
Err(error) => last_error = Some(error),
}
}
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("ad list unavailable")))
}
View on GitHub (pinned to 1f431ae49b)
Solutions
- Pass at least one ad-list URL — the simplest fix is to fall back to ads::DEFAULT_AD_LIST_URLS when the configured list is empty
- Audit the caller that builds the URL slice: an over-aggressive filter or an empty default from settings is the usual culprit
- If you instead saw a network-ish error, you are not hitting this branch — the propagated last_error names the real cause (DNS, proxy, HTTP status, JSON decode)
Example fix
// before: empty list reaches the fetcher and yields the generic error
let urls: Vec<String> = settings.ad_list_urls.unwrap_or_default();
let list = fetch_ad_list_from_urls(&urls).await?;
// after: fall back to defaults when the configured list is empty
let urls: Vec<String> = settings.ad_list_urls.filter(|u| !u.is_empty());
let urls: Vec<String> = if urls.is_empty() {
DEFAULT_AD_LIST_URLS.iter().map(|s| s.to_string()).collect()
} else { urls };
let list = fetch_ad_list_from_urls(&urls).await?; Defensive patterns
Strategy: validation
Validate before calling
// Before fetching, guarantee a non-empty candidate list
let urls: Vec<String> = configured_urls.into_iter().filter(|u| !u.trim().is_empty()).collect();
let urls = if urls.is_empty() {
DEFAULT_AD_LIST_URLS.iter().map(|s| s.to_string()).collect()
} else { urls };
assert!(!urls.is_empty(), "ad list needs at least one URL");
let list = fetch_ad_list_from_urls(&urls).await?; Type guard
fn has_ad_urls(urls: &[String]) -> bool { !urls.is_empty() } Try / catch
// The real network failure surfaces as the propagated last_error, not this message;
// match on it only to detect the empty-list config bug:
match fetch_ad_list_from_urls(&urls).await {
Ok(payload) => Ok(payload),
Err(e) if e.to_string() == "ad list unavailable" => {
// config bug: zero URLs were attempted — fix the source list
Err(anyhow!("ad list URL list is empty; check settings"))
}
Err(e) => Err(e), // real per-URL network/HTTP error is embedded here
} Prevention
- Default empty ad-list settings to DEFAULT_AD_LIST_URLS
- Never pass a filtered URL list without re-checking emptiness
- Treat 'ad list unavailable' as a configuration assertion failure, distinct from network errors
When it happens
Trigger: Calling fetch_ad_list_from_urls() with an empty &[] (or an empty configured URL list); the public fetch_ad_list() always passes DEFAULT_AD_LIST_URLS (2 entries) and can never produce this message on its own.
Common situations: A settings/config layer that lets users override the ad-list URL array is saved as an empty array; tests pass an empty Vec; code that filters URLs (e.g. removing invalid entries) accidentally filters everything out before calling.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/846f3ac92eddf9c9.
Report an issue: GitHub.