{"record":{"id":"da87f5129debaa88","repo":"BigPizzaV3/CodexPlusPlus","slug":"grok-alias-api-key","errorCode":null,"errorMessage":"Grok 模型 {alias} 不能同时替换并移除 API Key","messagePattern":"Grok 模型 (.+?) 不能同时替换并移除 API Key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/grok_config.rs","lineNumber":305,"sourceCode":"            bail!(\"Grok 模型来源别名重复：{source}\");\n        }\n        if !matches!(\n            input.api_backend.trim(),\n            \"responses\" | \"chat_completions\" | \"messages\"\n        ) {\n            bail!(\"Grok 模型 {alias} 的 API 协议无效\");\n        }\n        if input.context_window == Some(0) {\n            bail!(\"Grok 模型 {alias} 的上下文窗口必须大于 0\");\n        }\n        if input\n            .context_window\n            .is_some_and(|value| value > i64::MAX as u64)\n        {\n            bail!(\"Grok 模型 {alias} 的上下文窗口过大\");\n        }\n        if input.remove_api_key && !input.api_key_update.is_empty() {\n            bail!(\"Grok 模型 {alias} 不能同时替换并移除 API Key\");\n        }\n    }\n    Ok(())\n}\n\nfn table_string(doc: &DocumentMut, table: &str, key: &str) -> String {\n    doc.get(table)\n        .and_then(Item::as_table_like)\n        .and_then(|values| values.get(key))\n        .and_then(Item::as_str)\n        .unwrap_or_default()\n        .to_string()\n}\n\nfn item_string<T: TableLike + ?Sized>(table: &T, key: &str) -> String {\n    table\n        .get(key)\n        .and_then(Item::as_str)","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/grok_config.rs#L287-L323","documentation":"A model entry may either replace the API key (api_key_update non-empty) or mark it for removal (remove_api_key = true), but not both. validate_request rejects the combination because the two operations are contradictory — there is no defined outcome for 'set this key and remove it'.","triggerScenarios":"save_grok_config_at called with input.remove_api_key == true AND input.api_key_update non-empty, typically when a UI checkbox is left checked while a new key is also typed in, or when merging request objects.","commonSituations":"Editing a model to rotate the key while the 'remove key' toggle from a previous edit is still on; JSON merge patches layering both fields; scripted updates that always set both fields.","solutions":["Set remove_api_key = false and keep api_key_update with the new key to rotate","Clear api_key_update (empty string) and keep remove_api_key = true to delete the key","Reset the form state so only one of the two fields is ever populated"],"exampleFix":"// before\nremove_api_key: true, api_key_update: \"sk-new\"\n// after\nremove_api_key: false, api_key_update: \"sk-new\"","handlingStrategy":"validation","validationCode":"assert!(!(req.remove_api_key && !req.api_key_update.is_empty()), \"cannot replace and remove the key at once\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make replace/remove a single mutually exclusive radio choice in the UI","Clear related fields when toggling remove_api_key","Centralize request-building so the invariant is checked in one place"],"tags":["validation","conflicting-options","api-key"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}