{"record":{"id":"bf1700f1df82bacd","repo":"zeroclaw-labs/zeroclaw","slug":"header-key-value-must-be-a-string-got","errorCode":null,"errorMessage":"Header '{key}' value must be a string, got: {}","messagePattern":"Header '(.+?)' value must be a string, got: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":250,"sourceCode":"            \"GET\" => Ok(reqwest::Method::GET),\n            \"POST\" => Ok(reqwest::Method::POST),\n            \"PUT\" => Ok(reqwest::Method::PUT),\n            \"DELETE\" => Ok(reqwest::Method::DELETE),\n            \"PATCH\" => Ok(reqwest::Method::PATCH),\n            \"HEAD\" => Ok(reqwest::Method::HEAD),\n            \"OPTIONS\" => Ok(reqwest::Method::OPTIONS),\n            _ => anyhow::bail!(\n                \"Unsupported HTTP method: {method}. Supported: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS\"\n            ),\n        }\n    }\n\n    fn parse_headers(&self, headers: &serde_json::Value) -> anyhow::Result<HeaderMap> {\n        let mut result = HeaderMap::new();\n        if let Some(obj) = headers.as_object() {\n            for (key, value) in obj {\n                let Some(str_val) = value.as_str() else {\n                    anyhow::bail!(\"Header '{key}' value must be a string, got: {}\", value);\n                };\n                let header_name = HeaderName::from_str(key)\n                    .map_err(|e| anyhow::Error::msg(format!(\"Invalid header name '{key}': {e}\")))?;\n                let header_value = HeaderValue::from_str(str_val).map_err(|e| {\n                    anyhow::Error::msg(format!(\"Invalid value for header '{key}': {e}\"))\n                })?;\n                result.insert(header_name, header_value);\n            }\n        }\n        Ok(result)\n    }\n\n    fn validate_secret_name(secret_name: &str) -> anyhow::Result<()> {\n        if secret_name.is_empty() {\n            anyhow::bail!(\"auth_secret cannot be empty\");\n        }\n        if secret_name.len() > 64 {\n            anyhow::bail!(\"auth_secret must be 64 characters or fewer\");","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L232-L268","documentation":"Thrown by HttpRequestTool::parse_headers (crates/zeroclaw-tools/src/http_request.rs:250) when the headers argument is a JSON object but one of its values is not a JSON string. Every header value must be a string before it can be converted into an http::HeaderValue; numbers, booleans, null, arrays, and objects are rejected, and the error names the offending key. Only after this check are header name/value syntax validated.","triggerScenarios":"args.headers = {\"X-Retry-Count\": 42} or {\"X-Debug\": true} or {\"X-Tags\": [\"a\",\"b\"]}; LLM-generated tool calls that naturally emit JSON numbers for numeric headers; forwarding headers parsed from another JSON payload without stringification; {\"Content-Length\": 123}.","commonSituations":"Agent/model tool calls where numeric metadata headers are common (retry counts, IDs, timestamps); integrations copying a JSON config object straight into headers; version upgrades of callers that previously coerced values to strings.","solutions":["Stringify every header value in the args: {\"X-Retry-Count\": \"42\"}, {\"X-Debug\": \"true\"}.","If building the JSON programmatically, map values through to_string()/serde_json::Value::String.","For Authorization values, prefer the auth_secret parameter over literal headers."],"exampleFix":"// before\nlet args = json!({\"url\": u, \"headers\": {\"X-Retry-Count\": 42, \"Content-Type\": \"application/json\"}});\n\n// after\nlet args = json!({\"url\": u, \"headers\": {\"X-Retry-Count\": \"42\", \"Content-Type\": \"application/json\"}});","handlingStrategy":"type-guard","validationCode":"fn headers_values_are_strings(v: &serde_json::Value) -> bool {\n    v.as_object().map_or(true, |o| o.values().all(|x| x.is_string()))\n}","typeGuard":"fn headers_all_strings(v: &serde_json::Value) -> bool {\n    v.as_object().is_none_or(|o| o.values().all(|x| x.is_string()))\n}","tryCatchPattern":"let result = tool.execute(args).await?;\nif let Some(err) = &result.error {\n    if err.contains(\"value must be a string\") {\n        // stringify the named header (error includes the key) and retry\n    }\n}","preventionTips":["Type the headers parameter as map<string, string> in your tool-calling schema and validate before dispatch.","Convert numbers/booleans with to_string() when building args programmatically.","In prompt instructions, require string header values explicitly for model-generated calls."],"tags":["http","headers","json","validation","zeroclaw"],"backgroundTag":"invalid-header-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}