{"record":{"id":"3683b967fc457766","repo":"tinyhumansai/openhuman","slug":"brave-video-response-shape-changed-e","errorCode":null,"errorMessage":"Brave video response shape changed: {e}","messagePattern":"Brave video response shape changed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/search/tools/brave.rs","lineNumber":600,"sourceCode":"                \"freshness\": { \"type\": \"string\", \"description\": \"Time filter: 'pd', 'pw', 'pm', 'py'.\" }\n            },\n            \"required\": [\"query\"]\n        })\n    }\n\n    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {\n        let query = extract_query(&args)?;\n        let count = clamped_count(&args, self.cfg.max_results, 20);\n        let mut q: Vec<(&str, String)> = vec![(\"q\", query.clone()), (\"count\", count.to_string())];\n        if let Some(c) = pub_string(&args, \"country\") {\n            q.push((\"country\", c));\n        }\n        if let Some(f) = pub_string(&args, \"freshness\") {\n            q.push((\"freshness\", f));\n        }\n        let raw = brave_get(&self.cfg, \"/videos/search\", &q).await?;\n        let parsed: VideoResp = serde_json::from_value(raw)\n            .map_err(|e| anyhow::anyhow!(\"Brave video response shape changed: {e}\"))?;\n        if parsed.results.is_empty() {\n            return Ok(ToolResult::success(format!(\"No videos found for: {query}\")));\n        }\n        let mut lines = vec![format!(\"Video results for: {query} (via Brave)\")];\n        for (i, r) in parsed.results.iter().take(count).enumerate() {\n            let title = if r.title.trim().is_empty() {\n                \"Untitled\"\n            } else {\n                r.title.trim()\n            };\n            lines.push(format!(\"{}. {}\", i + 1, title));\n            lines.push(format!(\"   {}\", r.url.trim()));\n            if let Some(meta) = r.video.as_ref() {\n                if let Some(creator) = meta.creator.as_deref() {\n                    let creator = creator.trim();\n                    if !creator.is_empty() {\n                        lines.push(format!(\"   Creator: {creator}\"));\n                    }","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/search/tools/brave.rs#L582-L618","documentation":"Raised inside the Brave video-search tool's execute when deserializing the Brave VideoSearch API response fails because the JSON shape no longer matches the structs the tool models (missing/renamed fields, changed nesting). It is an upstream-contract-drift signal — the faulty input is Brave's response payload, not the user's query — surfaced to the agent transcript as a tool error rather than a panic.","triggerScenarios":"Thrown at src/openhuman/search/tools/brave.rs:600 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Compare the failing payload against current Brave VideoSearch API docs and update the response structs.","Make the deserialization tolerant: default optional fields, ignore unknown fields, or parse defensively field by field.","Add a pinned response fixture test so future Brave API changes fail in CI instead of in the field.","Retry the search with a different tool (e.g. web search) to still serve the user's request."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}