unicity-aos/aos-ce · warning

/v1/models discovery failed, advertising hardcoded catalog

Error message

/v1/models discovery failed, advertising hardcoded catalog: {e}

What it means

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.

Solutions

  1. Set the API key in the capsule configuration/environment so discovery can authenticate.
  2. Verify network egress to the provider and that the base URL implements GET /v1/models.
  3. Check the {e} detail: non-2xx → inspect provider status/quota; non-JSON → wrong base URL or proxy page.
  4. Accept the fallback if acceptable — the capsule still advertises the hardcoded catalog and functions.

Example fix

// before
// config: openai.base_url = "https://internal-proxy/" (no /v1/models)
Err(e) => {
    log::warn(format!("/v1/models discovery failed, advertising hardcoded catalog: {e}"));
    models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)
}
// after
// config: openai.base_url = "https://api.openai.com/v1", openai.api_key set
Err(e) => {
    log::warn(format!("/v1/models discovery failed, advertising hardcoded catalog: {e}"));
    models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)
}
Defensive patterns

Strategy: fallback

Validate before calling

// before calling describe, verify config
if (!config.apiKey || config.apiKey.trim() === '') throw new Error('missing api key');
const res = await fetch(config.baseUrl + '/v1/models', { headers: { Authorization: 'Bearer ' + config.apiKey } });
if (!res.ok) throw new Error('models endpoint non-2xx: ' + res.status);

Type guard

fn discovery_usable(cfg: &Config) -> bool {
    !cfg.api_key.trim().is_empty() && cfg.base_url.ends_with("/v1")
}

Try / catch

let entries = match models::discover(&cfg).await {
    Ok(live) => build_live_entries(&live, ...),
    Err(e) => { log::warn("discovery failed: {e}"); build_provider_entries(...) }
};

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/e625f8636631fe4b. Report an issue: GitHub.

Appendix: source

Thrown at capsules/capsule-openai/src/lib.rs:100

    /// fanned out to the caller under the new ABI.
    #[astrid::interceptor("llm_describe")]
    pub fn llm_describe(&self, _payload: serde_json::Value) -> Result<serde_json::Value, SysError> {
        // The env `model` is the *default selection* hint, not the only usable
        // model. We advertise the LIVE `/v1/models` catalogue (enriched from the
        // capability table) when reachable, and fall back to the full hardcoded
        // catalog otherwise so an offline/keyless install never regresses.
        let default_model = env::var("model").unwrap_or_else(|_| DEFAULT_MODEL.into());

        let entries = match Self::discover_models() {
            Ok(live_ids) => {
                // Live list is authority; enrich each id from the catalog and
                // hoist (or prepend) the configured default so it is first.
                models::build_live_entries(&live_ids, &default_model, REQUEST_TOPIC, STREAM_TOPIC)
            }
            Err(e) => {
                // Any discovery failure (missing/blank key, non-2xx, non-JSON,
                // empty data, network error) falls back to the full catalog.
                log::warn(format!(
                    "/v1/models discovery failed, advertising hardcoded catalog: {e}"
                ));
                models::build_provider_entries(&default_model, REQUEST_TOPIC, STREAM_TOPIC)
            }
        };

        let response = serde_json::json!({ "providers": entries });
        ipc::publish_json("llm.v1.response.describe", &response)?;
        Ok(response)
    }
}

impl OpenAIProvider {
    /// Build the `Authorization` header value from a raw configured key.
    ///
    /// Returns `Some("Bearer <trimmed>")` only when the key has non-whitespace
    /// content; a missing, empty, or whitespace/newline-only key (common from a
    /// copy-paste) is treated as **keyless** (`None`) so discovery never emits

View on GitHub (pinned to f6f22024fb)