BigPizzaV3/CodexPlusPlus · error · anyhow::Error

Base URL 不能为空

Error message

Base URL 不能为空

What it means

fetch_relay_profile_model_ids builds a request source from the RelayProfile, preferring an override base URL but otherwise using upstream_base_url. If the resulting trimmed base_url is empty, no models endpoint can be constructed, so it bails before any network call.

Solutions

  1. Set upstream_base_url on the RelayProfile (e.g. https://api.example.com) before fetching.
  2. If the UI is the source, require the Base URL field before enabling the 'fetch models' action.
  3. Trim-check profile.upstream_base_url client-side and show a validation error instead of calling.
  4. Re-save the profile ensuring the base URL persisted.

Example fix

// before
let ids = fetch_relay_profile_model_ids(&profile).await?; // profile.upstream_base_url == ""
// after
assert!(!profile.upstream_base_url.trim().is_empty());
let ids = fetch_relay_profile_model_ids(&profile).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn can_fetch_models(profile: &RelayProfile) -> bool {
    !profile.upstream_base_url.trim().is_empty()
}

Try / catch

if profile.upstream_base_url.trim().is_empty() {
    ui.require_field("Base URL");
} else {
    match fetch_relay_profile_model_ids(&profile).await { /* ... */ }
}

Prevention

When it happens

Trigger: Calling fetch_relay_profile_model_ids with a profile whose upstream_base_url (and any override) is empty or whitespace-only.

Common situations: Profile created but upstream URL never filled in; URL field cleared in the manager UI; profile loaded from an old/partial config file missing the field.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/fbb5791992d3d260. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/model_catalog.rs:721

    profile: &RelayProfile,
) -> anyhow::Result<(Vec<String>, String)> {
    let source = ModelSource {
        source_id: format!("relay-profile:{}", profile.id),
        source_type: "relay_profile".to_string(),
        name: if profile.name.trim().is_empty() {
            profile.id.clone()
        } else {
            profile.name.trim().to_string()
        },
        base_url: if profile.upstream_base_url.trim().is_empty() {
            profile.base_url.trim().to_string()
        } else {
            profile.upstream_base_url.trim().to_string()
        },
        api_key: profile.api_key.trim().to_string(),
    };
    if source.base_url.is_empty() {
        anyhow::bail!("Base URL 不能为空");
    }
    let endpoint = models_endpoint(&source.base_url);
    let client = crate::http_client::proxied_client(&profile.user_agent)?;
    let (models, status) = fetch_models_from_source(&client, &source).await;
    if models.is_empty() {
        let message = status
            .get("message")
            .and_then(Value::as_str)
            .unwrap_or("上游没有返回可用模型");
        anyhow::bail!("{message}");
    }
    Ok((models, endpoint))
}

fn preferred_responses_api_status(sources: &[Value]) -> Value {
    let statuses = sources
        .iter()
        .filter_map(|source| source.get("responses_api"))

View on GitHub (pinned to b1ed92e5e4)