{"record":{"id":"e6c340be3a7235c1","repo":"tonhowtf/omniget","slug":"msg","errorCode":null,"errorMessage":"{msg}","messagePattern":"\\{msg\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/ai_keys.rs","lineNumber":406,"sourceCode":"\npub async fn test(id: &str) -> anyhow::Result<KeyView> {\n    let entry = get(id)?;\n    let now = chrono::Utc::now().timestamp();\n    match models(&entry).await {\n        Ok(ids) => update(id, |e| {\n            e.last_ok = Some(true);\n            e.last_checked = Some(now);\n            e.models = ids.len();\n            e.error = None;\n        }),\n        Err(err) => {\n            let msg = err.to_string();\n            let _ = update(id, |e| {\n                e.last_ok = Some(false);\n                e.last_checked = Some(now);\n                e.error = Some(msg.clone());\n            });\n            Err(anyhow!(msg))\n        }\n    }\n}\n\nfn usd(v: f64) -> String {\n    if v.abs() < 0.01 {\n        format!(\"${:.4}\", v)\n    } else {\n        format!(\"${:.2}\", v)\n    }\n}\n\npub async fn balance(id: &str) -> anyhow::Result<KeyView> {\n    let entry = get(id)?;\n    let c = client()?;\n    let text: String = match entry.kind.as_str() {\n        \"openrouter\" => {\n            let j: serde_json::Value = c","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/ai_keys.rs#L388-L424","documentation":"In the test() function, when checking a key fails, the error message is captured, written into the key entry's state (last_ok=false, error field), and re-thrown as anyhow!(msg). The final message is whatever the underlying check produced — most commonly the HTTP error or JSON parse failure from the models/balance code path — so its content varies with the root cause.","triggerScenarios":"Calling test(id) on a key entry whose HTTP request fails (bad key, unreachable URL) or whose response cannot be parsed; the underlying error string is propagated verbatim.","commonSituations":"User clicks 'test key' in the UI with a revoked key, mistyped base_url, or offline network; stale entries whose provider endpoints changed.","solutions":["Read the stored error text on the key entry (or the thrown message) to identify the root cause (auth vs network vs parse).","Re-enter or rotate the API key if the message indicates 401/403.","Correct the base_url if the message shows 404 or HTML output.","Check network connectivity and provider status page for timeouts/5xx."],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"// verify the entry before testing\nlet entry = get(id)?;\nif entry.key.is_empty() { eprintln!(\"key entry has no key stored\"); }\nif entry.base_url.is_empty() { eprintln!(\"key entry has no base_url\"); }","typeGuard":"fn is_testable(e: &KeyEntry) -> bool { !e.key.is_empty() && !e.base_url.is_empty() }","tryCatchPattern":"match test(id) {\n    Ok(ok) => update_status(id, ok),\n    Err(e) => {\n        // test() already persisted err.to_string() on the entry; read e.error for the root cause\n        let stored = get(id).ok().and_then(|k| k.error);\n        diagnose(stored.as_deref().unwrap_or(&e.to_string()));\n    }\n}","preventionTips":["Run test() after every key edit or rotation","Read the entry's persisted error field to find the root cause","Keep base_url and key fields validated at entry-creation time","Check network before blaming the key"],"tags":["api","propagation","http"],"backgroundTag":"api-request-failed","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}