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

  1. 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
  2. Audit the caller that builds the URL slice: an over-aggressive filter or an empty default from settings is the usual culprit
  3. 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

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.