{"record":{"id":"684d1ba1a374d771","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-684d1b","errorCode":null,"errorMessage":"未找到可用的聚合供应商成员","messagePattern":"未找到可用的聚合供应商成员","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"crates/codex-plus-core/src/protocol_proxy.rs","lineNumber":713,"sourceCode":"                response: upstream,\n            });\n        }\n        let _ = crate::diagnostic_log::append_diagnostic_log(\n            \"protocol_proxy.upstream_failover\",\n            json!({\n                \"relayId\": relay.id,\n                \"relayName\": relay.name,\n                \"endpoint\": endpoint,\n                \"wireApi\": wire_api,\n                \"stream\": is_stream,\n                \"statusCode\": status_code,\n                \"attempt\": attempt + 1,\n                \"candidateCount\": relay_count,\n                \"headerTimeoutSeconds\": header_timeout.as_secs()\n            }),\n        );\n    }\n    anyhow::bail!(\"未找到可用的聚合供应商成员\")\n}\n\nfn select_model_route(\n    settings: &crate::settings::BackendSettings,\n    model: &str,\n) -> anyhow::Result<Option<ModelRouteSelection>> {\n    if model.is_empty() || settings.active_aggregate_relay_profile().is_some() {\n        return Ok(None);\n    }\n\n    let source = settings.active_relay_profile();\n    let Some(route) = source\n        .model_routes\n        .iter()\n        .find(|route| route.model.trim() == model)\n    else {\n        return Ok(None);\n    };","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/protocol_proxy.rs#L695-L731","documentation":"Thrown at the end of the failover loop in open_responses_proxy_request_with_settings_and_user_agent (crates/codex-plus-core/src/protocol_proxy.rs:713). The loop iterates the candidate relay list (primary selection plus fallback_relays_after members, or the single model-route target); on the last candidate it always returns, so this bail is reached only when the candidate list is empty — i.e. the aggregate rotation produced no usable member to try. In effect: the active aggregate provider has no members to dispatch the request to.","triggerScenarios":"Active relay is an aggregate whose members list is empty at request time; aggregate members were all deleted or their relay ids no longer resolve so the candidate list collapses to zero; racing settings edits removed members between selection and iteration.","commonSituations":"User creates an aggregate provider but never adds member relays; members deleted individually while the aggregate stayed active; settings file hand-edited to drop member entries; UI allowed saving an aggregate with zero members.","solutions":["Open the aggregate relay profile and add at least one member provider, then save","Verify every member's relay_id in the aggregate still matches an existing relay profile id (deleted members leave dangling ids)","Ensure each member relay has non-empty base_url and api_key so member validation passes","Alternatively switch the active relay away from the empty aggregate to a concrete provider profile"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn aggregate_has_usable_members(settings: &BackendSettings) -> bool {\n    settings\n        .active_aggregate_relay_profile()\n        .map(|agg| agg.members.iter().any(|m| {\n            settings.relay_profiles.iter().any(|p| {\n                p.id == m.relay_id\n                    && !p.base_url.trim().is_empty()\n                    && !p.api_key.trim().is_empty()\n            })\n        }))\n        .unwrap_or(true)\n}","typeGuard":null,"tryCatchPattern":"let result = open_responses_proxy_request(body, path).await;\nif let Err(e) = &result {\n    if e.to_string().contains(\"聚合供应商成员\") {\n        // prompt user to configure aggregate members before retrying\n    }\n}","preventionTips":["Block saving an aggregate profile with zero members in the manager UI","When deleting a relay profile, also remove it from every aggregate's member list","Health-check aggregate members (base_url + api_key non-empty) before activating the aggregate"],"tags":["aggregate-relay","relay-rotation","failover","empty-configuration"],"backgroundTag":"no-available-upstream-candidates","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}