zeroclaw-labs/zeroclaw · error · anyhow::Error
Header '{key}' value must be a string, got: {}
Error message
Header '{key}' value must be a string, got: {} What it means
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.
Source
Thrown at crates/zeroclaw-tools/src/http_request.rs:250
"GET" => Ok(reqwest::Method::GET),
"POST" => Ok(reqwest::Method::POST),
"PUT" => Ok(reqwest::Method::PUT),
"DELETE" => Ok(reqwest::Method::DELETE),
"PATCH" => Ok(reqwest::Method::PATCH),
"HEAD" => Ok(reqwest::Method::HEAD),
"OPTIONS" => Ok(reqwest::Method::OPTIONS),
_ => anyhow::bail!(
"Unsupported HTTP method: {method}. Supported: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS"
),
}
}
fn parse_headers(&self, headers: &serde_json::Value) -> anyhow::Result<HeaderMap> {
let mut result = HeaderMap::new();
if let Some(obj) = headers.as_object() {
for (key, value) in obj {
let Some(str_val) = value.as_str() else {
anyhow::bail!("Header '{key}' value must be a string, got: {}", value);
};
let header_name = HeaderName::from_str(key)
.map_err(|e| anyhow::Error::msg(format!("Invalid header name '{key}': {e}")))?;
let header_value = HeaderValue::from_str(str_val).map_err(|e| {
anyhow::Error::msg(format!("Invalid value for header '{key}': {e}"))
})?;
result.insert(header_name, header_value);
}
}
Ok(result)
}
fn validate_secret_name(secret_name: &str) -> anyhow::Result<()> {
if secret_name.is_empty() {
anyhow::bail!("auth_secret cannot be empty");
}
if secret_name.len() > 64 {
anyhow::bail!("auth_secret must be 64 characters or fewer");View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before
let args = json!({"url": u, "headers": {"X-Retry-Count": 42, "Content-Type": "application/json"}});
// after
let args = json!({"url": u, "headers": {"X-Retry-Count": "42", "Content-Type": "application/json"}}); Defensive patterns
Strategy: type-guard
Validate before calling
fn headers_values_are_strings(v: &serde_json::Value) -> bool {
v.as_object().map_or(true, |o| o.values().all(|x| x.is_string()))
} Type guard
fn headers_all_strings(v: &serde_json::Value) -> bool {
v.as_object().is_none_or(|o| o.values().all(|x| x.is_string()))
} Try / catch
let result = tool.execute(args).await?;
if let Some(err) = &result.error {
if err.contains("value must be a string") {
// stringify the named header (error includes the key) and retry
}
} Prevention
- 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.
When it happens
Trigger: 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}.
Common situations: 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.
Related errors
- AIEOS payload must be a JSON object
- URL cannot contain whitespace
- Only http:// and https:// URLs are allowed
- Unsupported HTTP method: {method}. Supported: GET, POST, PUT
- auth_secret cannot be empty
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/bf1700f1df82bacd.
Report an issue: GitHub.