FoundationAgents/MetaGPT · error · TypeError
Headers must be a dictionary
Error message
Headers must be a dictionary
What it means
Raised by OpenAIRequestor._validate_headers when default_headers supplied to a request is not a Python dict. The requestor validates all caller-supplied headers before merging them with auth headers, so a non-mapping value (list, string, None-like sentinel object, tuple) fails immediately with this TypeError.
Source
Thrown at metagpt/provider/general_api_base.py:427
if self.organization:
headers["LLM-Organization"] = self.organization
if self.api_version is not None and self.api_type == ApiType.OPEN_AI:
headers["LLM-Version"] = self.api_version
if request_id is not None:
headers["X-Request-Id"] = request_id
headers.update(extra)
return headers
def _validate_headers(self, supplied_headers: Optional[Dict[str, str]]) -> Dict[str, str]:
headers: Dict[str, str] = {}
if supplied_headers is None:
return headers
if not isinstance(supplied_headers, dict):
raise TypeError("Headers must be a dictionary")
for k, v in supplied_headers.items():
if not isinstance(k, str):
raise TypeError("Header keys must be strings")
if not isinstance(v, str):
raise TypeError("Header values must be strings")
headers[k] = v
# NOTE: It is possible to do more validation of the headers, but a request could always
# be made to the API manually with invalid headers, so we need to handle them server side.
return headers
def _prepare_request_raw(
self,
url,
supplied_headers,
method,View on GitHub (pinned to 11cdf466d0)
Solutions
- Pass default_headers as a plain dict of strings, e.g. {"X-Org-Id": "42"}, or omit/None it.
- If headers arrive as a JSON string, parse with json.loads first: default_headers=json.loads(raw).
- If headers are a list of (k, v) pairs, convert with dict(pairs).
- Ensure custom provider subclasses do not override default_headers with a non-dict attribute.
Example fix
# before
headers = '[{"X-Tenant": "acme"}]' # str -> TypeError
req.request(..., supplied_headers=headers)
# after
headers = {"X-Tenant": "acme"}
req.request(..., supplied_headers=headers) Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_headers(h):
if h is None:
return {}
if isinstance(h, str):
import json; h = json.loads(h)
if isinstance(h, (list, tuple)):
h = dict(h)
if not isinstance(h, dict):
raise TypeError(f"headers must be dict, got {type(h).__name__}")
return {str(k): str(v) for k, v in h.items()}
supplied = normalize_headers(raw_headers) Type guard
def is_valid_headers(h) -> TypeGuard[Optional[Dict[str, str]]]:
return h is None or (isinstance(h, dict) and all(isinstance(k, str) and isinstance(v, str) for k, v in h.items())) Try / catch
try:
requestor._validate_headers(supplied)
except TypeError as e:
raise ValueError(f"Bad headers payload: {e}; pass dict[str, str]") from e Prevention
- Always json.loads header strings from configs or CLI
- Normalize headers through one helper before any request
- Type default_headers as Optional[Dict[str, str]] in your config models
When it happens
Trigger: Passing default_headers as anything but a dict/None when constructing or invoking a provider request, e.g. default_headers="[{\"k\":"v\"}]" (a JSON string), a list of tuples, or a requests-compatible Headers object that is not a dict subclass.
Common situations: Deserializing headers from JSON/YAML without json.loads, piping headers from a CLI argument as a string, or reusing a config structure that stores headers as a list of pairs.
Related errors
- Header keys must be strings
- Header values must be strings
- 'openai.proxy' must be specified as either a string URL or a
- Missing fields: {missing_fields}
- Only support for python, markdown, but got {language}
AI-assisted analysis of FoundationAgents/MetaGPT@11cdf466d0 (2026-08-14).
Data as JSON: /api/errors/b77d6be5f3e1f0e6.
Report an issue: GitHub.