{"record":{"id":"882f69f8f615acb4","repo":"BigPizzaV3/CodexPlusPlus","slug":"model-metadata-json-error","errorCode":null,"errorMessage":"model_metadata JSON 解析失败：{error}","messagePattern":"model_metadata JSON 解析失败：(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":2141,"sourceCode":"    }\n    Ok(())\n}\n\nfn validate_model_auto_compact(model_auto_compact: &HashMap<String, String>) -> anyhow::Result<()> {\n    for (slug, value) in model_auto_compact {\n        if crate::model_suffix::parse_compact_percent(value).is_none() {\n            anyhow::bail!(\"model_auto_compact 的模型 {slug} 百分比无效：{value}\");\n        }\n    }\n    Ok(())\n}\n\nfn parse_model_metadata_map(metadata_json: &str) -> anyhow::Result<serde_json::Map<String, Value>> {\n    if metadata_json.trim().is_empty() {\n        return Ok(serde_json::Map::new());\n    }\n    let value: Value = serde_json::from_str(metadata_json)\n        .map_err(|error| anyhow::anyhow!(\"model_metadata JSON 解析失败：{error}\"))?;\n    let map = value\n        .as_object()\n        .ok_or_else(|| anyhow::anyhow!(\"model_metadata 必须是 JSON 对象\"))?;\n    for (slug, metadata) in map {\n        if !metadata.is_object() {\n            anyhow::bail!(\"model_metadata 的模型 {slug} 值必须是对象\");\n        }\n    }\n    Ok(map.clone())\n}\n\nfn model_metadata_has_entries(\n    metadata: &serde_json::Map<String, Value>,\n    entry_slugs: &HashSet<String>,\n) -> bool {\n    metadata.keys().any(|slug| entry_slugs.contains(slug))\n}\n","sourceCodeStart":2123,"sourceCodeEnd":2159,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/relay_config.rs#L2123-L2159","documentation":"parse_model_metadata_map parses the profile's model_metadata field into serde_json::Map<String, Value>. On serde_json failure it throws \"model_metadata JSON 解析失败：{error}\". Empty strings yield an empty map, so only non-empty malformed JSON triggers the error. Called from apply_model_catalog_to_config and normalize_relay_profile_for_storage.","triggerScenarios":"Applying/saving a profile whose model_metadata is non-empty but invalid JSON — e.g. missing quotes around keys, trailing commas, or pasted JavaScript object literal text.","commonSituations":"Hand-writing metadata in a UI field with JS syntax ({slug: {...}}), comments in JSON, or truncation when copy-pasting large metadata blobs.","solutions":["Fix the JSON syntax reported in the error detail (line/column from serde_json).","Ensure the shape is {\"<model-slug>\": {...}, ...} with double-quoted keys and valid JSON values.","Validate with JSON.parse/serde_json before saving; or clear the field to skip metadata overrides.","Generate the JSON programmatically instead of hand-editing."],"exampleFix":"// before\nmodel_metadata = \"{deepseek-v4-pro: {context_window: 1000000}}\"\n// after\nmodel_metadata = \"{\\\"deepseek-v4-pro\\\": {\\\"context_window\\\": 1000000}}\"","handlingStrategy":"validation","validationCode":"fn metadata_json_is_valid(json: &str) -> bool {\n    if json.trim().is_empty() { return true; }\n    serde_json::from_str::<serde_json::Value>(json)\n        .map(|v| v.is_object())\n        .unwrap_or(false)\n}","typeGuard":"fn is_metadata_map(v: &serde_json::Value) -> bool {\n    v.as_object().map(|m| m.values().all(|x| x.is_object())).unwrap_or(false)\n}","tryCatchPattern":"match apply_model_catalog_to_config(...) {\n    Err(e) if e.to_string().contains(\"model_metadata JSON 解析失败\") => {\n        // surface serde_json's line/column and reject the save in the UI\n    }\n    other => other?,\n}","preventionTips":["Generate model_metadata JSON programmatically, not by hand","Validate with JSON.parse/serde_json on input","Leave the field empty when no metadata overrides are needed","Avoid JS object-literal syntax (unquoted keys, comments) in the field"],"tags":["json","parse-error","metadata"],"backgroundTag":"json-parse-error","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"}