{"record":{"id":"e625f8636631fe4b","repo":"unicity-aos/aos-ce","slug":"v1-models-discovery-failed-advertising-hardcoded-catalog-e","errorCode":null,"errorMessage":"/v1/models discovery failed, advertising hardcoded catalog: {e}","messagePattern":"/v1/models discovery failed, advertising hardcoded catalog: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-openai/src/lib.rs","lineNumber":100,"sourceCode":"    /// fanned out to the caller under the new ABI.\n    #[astrid::interceptor(\"llm_describe\")]\n    pub fn llm_describe(&self, _payload: serde_json::Value) -> Result<serde_json::Value, SysError> {\n        // The env `model` is the *default selection* hint, not the only usable\n        // model. We advertise the LIVE `/v1/models` catalogue (enriched from the\n        // capability table) when reachable, and fall back to the full hardcoded\n        // catalog otherwise so an offline/keyless install never regresses.\n        let default_model = env::var(\"model\").unwrap_or_else(|_| DEFAULT_MODEL.into());\n\n        let entries = match Self::discover_models() {\n            Ok(live_ids) => {\n                // Live list is authority; enrich each id from the catalog and\n                // hoist (or prepend) the configured default so it is first.\n                models::build_live_entries(&live_ids, &default_model, REQUEST_TOPIC, STREAM_TOPIC)\n            }\n            Err(e) => {\n                // Any discovery failure (missing/blank key, non-2xx, non-JSON,\n                // empty data, network error) falls back to the full catalog.\n                log::warn(format!(\n                    \"/v1/models discovery failed, advertising hardcoded catalog: {e}\"\n                ));\n                models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)\n            }\n        };\n\n        let response = serde_json::json!({ \"providers\": entries });\n        ipc::publish_json(\"llm.v1.response.describe\", &response)?;\n        Ok(response)\n    }\n}\n\nimpl OpenAIProvider {\n    /// Build the `Authorization` header value from a raw configured key.\n    ///\n    /// Returns `Some(\"Bearer <trimmed>\")` only when the key has non-whitespace\n    /// content; a missing, empty, or whitespace/newline-only key (common from a\n    /// copy-paste) is treated as **keyless** (`None`) so discovery never emits","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-openai/src/lib.rs#L82-L118","documentation":"capsule-openai's llm_describe first tries to discover the live model list via the provider's /v1/models endpoint. On any discovery failure (missing/blank API key, non-2xx status, non-JSON body, empty data, or network error) it logs this warning and falls back to advertising the full hardcoded model catalog instead of failing the describe call.","triggerScenarios":"The /v1/models discovery request fails inside llm_describe: API key missing or blank in config, the OpenAI-compatible endpoint returns non-2xx or non-JSON, the data array is empty, or a network error occurs.","commonSituations":"OPENAI_API_KEY not set or empty in the capsule environment; pointing the capsule at a proxy that doesn't implement /v1/models; network egress blocked from the capsule; provider outage.","solutions":["Set the API key in the capsule configuration/environment so discovery can authenticate.","Verify network egress to the provider and that the base URL implements GET /v1/models.","Check the {e} detail: non-2xx → inspect provider status/quota; non-JSON → wrong base URL or proxy page.","Accept the fallback if acceptable — the capsule still advertises the hardcoded catalog and functions."],"exampleFix":"// before\n// config: openai.base_url = \"https://internal-proxy/\" (no /v1/models)\nErr(e) => {\n    log::warn(format!(\"/v1/models discovery failed, advertising hardcoded catalog: {e}\"));\n    models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)\n}\n// after\n// config: openai.base_url = \"https://api.openai.com/v1\", openai.api_key set\nErr(e) => {\n    log::warn(format!(\"/v1/models discovery failed, advertising hardcoded catalog: {e}\"));\n    models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)\n}","handlingStrategy":"fallback","validationCode":"// before calling describe, verify config\nif (!config.apiKey || config.apiKey.trim() === '') throw new Error('missing api key');\nconst res = await fetch(config.baseUrl + '/v1/models', { headers: { Authorization: 'Bearer ' + config.apiKey } });\nif (!res.ok) throw new Error('models endpoint non-2xx: ' + res.status);","typeGuard":"fn discovery_usable(cfg: &Config) -> bool {\n    !cfg.api_key.trim().is_empty() && cfg.base_url.ends_with(\"/v1\")\n}","tryCatchPattern":"let entries = match models::discover(&cfg).await {\n    Ok(live) => build_live_entries(&live, ...),\n    Err(e) => { log::warn(\"discovery failed: {e}\"); build_provider_entries(...) }\n};","preventionTips":["Set and verify the API key in capsule config","Point base_url at an endpoint implementing /v1/models","Monitor discovery failures and alert on sustained fallback"],"tags":["openai","http","fallback","model-discovery"],"backgroundTag":"api-error-response","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}