{"record":{"id":"3067e88bcf002af4","repo":"Hmbown/CodeWhale","slug":"failed-to-parse-ollama-api-tags-json-err","errorCode":null,"errorMessage":"Failed to parse Ollama /api/tags JSON: {err}","messagePattern":"Failed to parse Ollama /api/tags JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/local_ollama.rs","lineNumber":84,"sourceCode":"\n#[derive(Debug, Deserialize)]\nstruct OllamaTagsResponse {\n    #[serde(default)]\n    models: Vec<OllamaTagModel>,\n}\n\n#[derive(Debug, Deserialize)]\nstruct OllamaTagModel {\n    #[serde(default)]\n    name: String,\n    #[serde(default)]\n    model: String,\n}\n\n/// Parse native Ollama `GET /api/tags` JSON into sorted unique tag ids.\npub(crate) fn parse_ollama_tags_response(payload: &str) -> anyhow::Result<Vec<String>> {\n    let parsed: OllamaTagsResponse = serde_json::from_str(payload)\n        .map_err(|err| anyhow::anyhow!(\"Failed to parse Ollama /api/tags JSON: {err}\"))?;\n    let mut tags: Vec<String> = parsed\n        .models\n        .into_iter()\n        .filter_map(|row| {\n            let name = row.name.trim();\n            if !name.is_empty() {\n                return Some(name.to_string());\n            }\n            let model = row.model.trim();\n            if !model.is_empty() {\n                Some(model.to_string())\n            } else {\n                None\n            }\n        })\n        .collect();\n    tags.sort();\n    tags.dedup();","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/local_ollama.rs#L66-L102","documentation":"Raised when the native Ollama `GET /api/tags` response body fails `serde_json` deserialization into the OllamaTagsResponse struct. The payload is either not JSON at all (HTML error page, proxy response) or does not match the expected shape (missing `models` array, wrong field types).","triggerScenarios":"`parse_ollama_tags_response` receiving a body that is empty, truncated, HTML/plain text, or whose `models` entries lack required fields with the expected types.","commonSituations":"Ollama not running so a proxy/portal answers with HTML; Ollama version with a changed /api/tags schema; a reverse proxy returning an error page; hitting a non-Ollama port.","solutions":["Verify the Ollama server is reachable at the expected host/port (`curl http://localhost:11434/api/tags`)","Log or print the raw payload to see what was actually returned","Update the OllamaTagsResponse struct / check the Ollama version's schema","Skip or downgrade the local catalog probe on parse failure instead of failing the listing"],"exampleFix":"// before\nlet parsed: OllamaTagsResponse = serde_json::from_str(payload)\n    .map_err(|err| anyhow::anyhow!(\"Failed to parse Ollama /api/tags JSON: {err}\"))?;\n// after (tolerate non-JSON bodies)\nmatch serde_json::from_str::<OllamaTagsResponse>(payload) {\n    Ok(parsed) => { /* ... */ }\n    Err(err) => tracing::warn!(\"ollama /api/tags not JSON: {err}; body starts: {}\", &payload[..payload.len().min(120)]),\n}","handlingStrategy":"validation","validationCode":"function looksLikeOllamaTags(body) {\n  const j = safeJsonParse(body);\n  return j && Array.isArray(j.models) && j.models.every(m => typeof m.name === 'string');\n}","typeGuard":"fn is_tags_payload(v: &serde_json::Value) -> bool {\n    v.get(\"models\").map(|m| m.is_array()).unwrap_or(false)\n}","tryCatchPattern":"match parse_ollama_tags_response(&body) {\n    Ok(tags) => tags,\n    Err(e) => { log::warn!(\"ollama catalog unavailable: {e}\"); Vec::new() }\n}","preventionTips":["Health-check the Ollama endpoint before parsing","Log the first bytes of any non-JSON body for diagnosis","Pin/verify the Ollama version when the schema changes","Treat local catalog probe failure as non-fatal"],"tags":["ollama","json","serde","parsing"],"backgroundTag":"json-parse-error","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}