BigPizzaV3/CodexPlusPlus · error

上游 Key 不能为空

Error message

上游 Key 不能为空

What it means

Thrown by validate_upstream in crates/codex-plus-core/src/protocol_proxy.rs when a relay candidate picked to serve a proxied request has an empty api_key. Requests are authenticated with bearer_auth(relay.api_key); the proxy fails fast on blank keys instead of sending an unauthenticated request that would surface as an upstream 401.

Source

Thrown at crates/codex-plus-core/src/protocol_proxy.rs:1011

) -> reqwest::RequestBuilder {
    let mut builder = client
        .post(endpoint)
        .bearer_auth(api_key)
        .header(reqwest::header::CONTENT_TYPE, "application/json");
    if is_stream {
        builder = builder
            .header(reqwest::header::ACCEPT, "text/event-stream")
            .header(reqwest::header::CACHE_CONTROL, "no-cache");
    }
    builder.json(upstream_body)
}

fn validate_upstream(relay: &crate::settings::RelayProfile) -> anyhow::Result<()> {
    if relay.base_url.trim().is_empty() {
        anyhow::bail!("上游 Base URL 不能为空");
    }
    if relay.api_key.trim().is_empty() {
        anyhow::bail!("上游 Key 不能为空");
    }
    Ok(())
}

fn conversation_id_from_responses_request(body: &Value) -> Option<String> {
    for key in ["conversation", "conversation_id", "previous_response_id"] {
        if let Some(value) = body.get(key).and_then(Value::as_str) {
            let value = value.trim();
            if !value.is_empty() {
                return Some(value.to_string());
            }
        }
    }
    None
}

fn effective_user_agent(configured_user_agent: &str, original_user_agent: Option<&str>) -> String {
    let configured_user_agent = configured_user_agent.trim();

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Find the failing relay via the diagnostic log (protocol_proxy.upstream_request includes relayName) and set its API key
  2. Remove or disable keyless member profiles from the aggregate so rotation skips them
  3. When importing providers, always include the apiKey so the created profile is complete
Defensive patterns

Strategy: validation

Validate before calling

fn all_candidates_have_api_key(settings: &BackendSettings) -> bool {
    settings
        .relay_profiles
        .iter()
        .filter(|p| settings
            .active_aggregate_relay_profile()
            .map(|a| a.members.iter().any(|m| m.relay_id == p.id))
            .unwrap_or(p.id == settings.active_relay_id))
        .all(|p| !p.api_key.trim().is_empty())
}

Prevention

When it happens

Trigger: Rotation lands on an aggregate member whose api_key is blank; the active relay's key was cleared or never entered; a model-route target profile has no key.

Common situations: Member profiles added to an aggregate as placeholders; key rotation left a profile temporarily keyless while it stayed in the pool; imported profiles whose apiKey parameter was omitted.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/4943931c336951ca. Report an issue: GitHub.