{"record":{"id":"6d1b3edf169e79fa","repo":"farion1231/cc-switch","slug":"volcengine-handled-via-ak-sk-branch-above","errorCode":null,"errorMessage":"volcengine handled via AK/SK branch above","messagePattern":"volcengine handled via AK/SK branch above","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/coding_plan.rs","lineNumber":1466,"sourceCode":"\n    // 其余供应商：数据面 Bearer api_key。\n    // 与 balance::get_balance 一致：给出明确错误，避免 footer 显示无信息的失败\n    if api_key.trim().is_empty() {\n        return Ok(coding_plan_not_found(\"API key is empty\"));\n    }\n\n    match provider {\n        CodingPlanProvider::Kimi => query_kimi(api_key).await,\n        CodingPlanProvider::ZhipuCn | CodingPlanProvider::ZhipuEn => {\n            query_zhipu(base_url, api_key).await\n        }\n        CodingPlanProvider::MiniMaxCn => query_minimax(api_key, true).await,\n        CodingPlanProvider::MiniMaxEn => query_minimax(api_key, false).await,\n        CodingPlanProvider::ZenMux => query_zenmux(base_url, api_key).await,\n        CodingPlanProvider::OpencodeGo => query_opencode_go(api_key).await,\n        // 火山已在上面的 AK/SK 分支提前返回，此处不可达。\n        CodingPlanProvider::Volcengine => {\n            unreachable!(\"volcengine handled via AK/SK branch above\")\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{\n        detect_provider, parse_afp_tiers, parse_coding_plan_tiers, parse_minimax_tiers,\n        parse_opencode_go_tiers, parse_zhipu_token_tiers, query_zhipu_team_at,\n        volcengine_canonical_query, volcengine_is_auth_error_code, volcengine_region,\n        volcengine_response_error, volcengine_sign, zhipu_quota_base, CodingPlanProvider,\n        TIER_FIVE_HOUR, TIER_MONTHLY, TIER_WEEKLY_LIMIT,\n    };\n    use serde_json::json;\n\n    #[test]\n    fn opencode_go_detects_both_base_variants_but_not_zen() {\n        // claude/claude-desktop 预设 base 是 /zen/go，codex/opencode/pi 是","sourceCodeStart":1448,"sourceCodeEnd":1484,"githubUrl":"https://github.com/farion1231/cc-switch/blob/3217f72596f2d1c0f879f0a05f83803825d9809f/src-tauri/src/services/coding_plan.rs#L1448-L1484","documentation":"This is a Rust `unreachable!()` panic used as a defensive guard in `get_coding_plan_quota` (src-tauri/src/services/coding_plan.rs:1466). Volcengine is supposed to be routed to a dedicated AK/SK-signing branch that returns early (lines 1438-1447), because it authenticates with Volcengine control-plane AccessKey ID + Secret instead of the data-plane Bearer API key used by all other providers. If control flow ever reaches the `CodingPlanProvider::Volcengine` match arm at the bottom, it means the early AK/SK branch was skipped or removed — an internal invariant violation, not a user-facing condition. The library panics deliberately to fail loudly during development rather than silently query Volcengine with the wrong auth scheme.","triggerScenarios":"The only way to trigger it is a code regression: `detect_provider(base_url)` returns `CodingPlanProvider::Volcengine` but the preceding `if let CodingPlanProvider::Volcengine = provider { ... }` early-return block no longer executes or was deleted — e.g. someone adds a new provider branch above that mistakenly swallows/redirects the Volcengine case, refactors the AK/SK branch into a `match` that falls through, or edits the match arms without updating the unreachable guard. It cannot be triggered by any API input (base_url, api_key, AK/SK values) in the current code.","commonSituations":"Seen during development after refactoring `get_coding_plan_quota`'s provider routing, after adding new `CodingPlanProvider` enum variants, or when a contributor merges a change to the Volcengine early-return block without re-running tests. End users never hit it in a released build.","solutions":["Inspect the code between `detect_provider` and the final `match`: confirm the `if let CodingPlanProvider::Volcengine = provider` block (lines 1438-1447) is still present, still returns early, and was not bypassed by an earlier `return`/branch.","Check whether a recent commit moved or restructured the AK/SK early-return (e.g. converted the `if let` into a `match` on provider placed after the final match); restore the early return before the api_key check.","Update the exhaustive match if a new provider variant was added and Volcengine handling was accidentally relocated; keep the `unreachable!()` only if the AK/SK branch provably returns for every Volcengine path.","Run the existing test suite (the `#[cfg(test)]` module exercises this routing) and add a test asserting a Volcengine base_url with AK/SK credentials never reaches the panic arm.","If Volcengine legitimately needs data-plane handling in the future, replace the `unreachable!()` arm with a real `query_volcengine`-style call instead of keeping a stale guard."],"exampleFix":"// before (regression: AK/SK early-return removed)\nlet provider = match detect_provider(base_url) { ... };\nif api_key.trim().is_empty() { ... }\nmatch provider {\n    CodingPlanProvider::Volcengine => unreachable!(\"volcengine handled via AK/SK branch above\"),\n    _ => ...\n}\n// after (restore the AK/SK branch before the api_key check)\nlet provider = match detect_provider(base_url) { ... };\nif let CodingPlanProvider::Volcengine = provider {\n    let (ak, sk) = (access_key_id.unwrap_or(\"\").trim(), secret_access_key.unwrap_or(\"\").trim());\n    if ak.is_empty() || sk.is_empty() {\n        return Ok(coding_plan_not_found(\"Volcengine usage query needs the account AccessKey ID + Secret (not the inference API key)\"));\n    }\n    return query_volcengine(base_url, ak, sk).await;\n}\nif api_key.trim().is_empty() { ... }\nmatch provider { /* Volcengine arm stays unreachable */ }","handlingStrategy":"validation","validationCode":"// Caller-side check before invoking the quota API: ensure Volcengine\n// requests supply AK/SK credentials so the early branch handles them.\nfn validate_quota_request(base_url: &str, ak: Option<&str>, sk: Option<&str>) -> Result<(), String> {\n    if is_volcengine_base_url(base_url) {\n        match (ak, sk) {\n            (Some(ak), Some(sk)) if !ak.trim().is_empty() && !sk.trim().is_empty() => Ok(()),\n            _ => Err(\"Volcengine requires AccessKey ID + Secret (not the inference API key)\".into()),\n        }\n    } else {\n        Ok(())\n    }\n}","typeGuard":"fn is_volcengine(provider: &CodingPlanProvider) -> bool {\n    matches!(provider, CodingPlanProvider::Volcengine)\n}","tryCatchPattern":"// Rust has no catch for panics in the same task; isolate with catch_unwind\n// and surface a deterministic failure instead of crashing the app:\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(||\n    futures::executor::block_on(get_coding_plan_quota(base_url, api_key, ak, sk, provider, org, proj))\n));\nmatch result {\n    Ok(Ok(quota)) => render_quota(quota),\n    Ok(Err(transient)) => retry_with_backoff(transient),\n    Err(_panic) => render_error(\"Internal routing error: provider handling inconsistent\"),\n}","preventionTips":["Never remove or reorder the AK/SK early-return block for Volcengine without updating the final match arm.","Add a unit test per CodingPlanProvider variant asserting get_coding_plan_quota routes without panicking.","When adding a new enum variant, fix the exhaustive match immediately rather than using a catch-all arm that hides regressions.","Run `cargo test` on the services module in CI so any path reaching the `unreachable!()` arm fails the build.","Keep provider routing (detect + branch) in one function so the invariant 'Volcengine returns early' is locally visible."],"tags":["rust","panic","unreachable","invariant-violation","provider-routing"],"backgroundTag":"unreachable-panic-invariant-violation","analyzedSha":"3217f72596f2d1c0f879f0a05f83803825d9809f","analyzedAt":"2026-08-29T00:42:39.619Z","contentChangedAt":"2026-08-29T00:42:39.619Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}