{"record":{"id":"846f3ac92eddf9c9","repo":"BigPizzaV3/CodexPlusPlus","slug":"ad-list-unavailable","errorCode":null,"errorMessage":"ad list unavailable","messagePattern":"ad list unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/ads.rs","lineNumber":166,"sourceCode":"    let cache_bust = SystemTime::now()\n        .duration_since(UNIX_EPOCH)\n        .map(|duration| duration.as_millis())\n        .unwrap_or_default();\n    let mut last_error = None;\n    for url in urls {\n        let url = cache_busted_ad_url(url.as_ref(), cache_bust);\n        let result = async {\n            let response = client.get(url).send().await?.error_for_status()?;\n            let payload = response.json::<Value>().await?;\n            Ok::<_, anyhow::Error>(normalize_ad_payload(payload))\n        }\n        .await;\n        match result {\n            Ok(payload) => return Ok(payload),\n            Err(error) => last_error = Some(error),\n        }\n    }\n    Err(last_error.unwrap_or_else(|| anyhow::anyhow!(\"ad list unavailable\")))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn sponsored(id: &str) -> Value {\n        json!({\n            \"id\": id,\n            \"type\": \"sponsor\",\n            \"title\": id,\n            \"description\": \"描述\",\n            \"url\": \"https://example.com/\",\n        })\n    }\n\n    #[test]\n    fn top_ad_is_kept_out_of_the_recommendation_pool() {","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/ads.rs#L148-L184","documentation":"Sentinel error returned by `fetch_ad_list_from_urls` when none of the configured ad-list URLs produced a payload. The function iterates the URLs, storing each failure in `last_error`, and if the URL list is empty (or no URL was attempted at all) there is no `last_error` to propagate, so this generic fallback is raised instead. Callers `fetch_ad_list` and the backup-URL retry helper surface it as 'ad list unavailable'.","triggerScenarios":"Calling `fetch_ad_list`/`fetch_ad_list_tries_backup_url_when_primary_fails` when the configured ad URL list is empty, so the loop body never runs and `last_error` stays None; each attempted URL's real error is stored but this message is returned only when no URL was tried.","commonSituations":"Fresh install with no ad-list URLs configured in settings, config migration wiped the URL list, or a code path passes an empty URL slice.","solutions":["Configure at least one ad-list URL in the settings that feeds fetch_ad_list","Verify the URL list is not accidentally emptied by config load/merge logic","If the real network error is expected, ensure the loop records `last_error` (it does) and check logs for the per-URL error","Improve the fallback to include the URL list state for debuggability"],"exampleFix":"// before\nErr(last_error.unwrap_or_else(|| anyhow::anyhow!(\"ad list unavailable\")))\n// after\nErr(last_error.unwrap_or_else(|| {\n    anyhow::anyhow!(\"ad list unavailable: no ad list URLs configured (configured=0)\")\n}))","handlingStrategy":"validation","validationCode":"if ad_list_urls.is_empty() {\n    return Err(anyhow!(\"no ad list URLs configured; add at least one URL\"));\n}","typeGuard":null,"tryCatchPattern":"match fetch_ad_list().await {\n    Err(e) if e.to_string() == \"ad list unavailable\" => configure_default_ad_urls(),\n    Err(e) => return Err(e),\n    Ok(list) => apply(list),\n}","preventionTips":["Validate that the ad URL list is non-empty at settings load time","Seed sensible default URLs on first run","Log per-URL fetch errors so the sentinel is never the only clue"],"tags":["network","configuration","empty-input","ad-list"],"backgroundTag":"empty-required-field","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}