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

  1. Pass default_headers as a plain dict of strings, e.g. {"X-Org-Id": "42"}, or omit/None it.
  2. If headers arrive as a JSON string, parse with json.loads first: default_headers=json.loads(raw).
  3. If headers are a list of (k, v) pairs, convert with dict(pairs).
  4. 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

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


AI-assisted analysis of FoundationAgents/MetaGPT@11cdf466d0 (2026-08-14). Data as JSON: /api/errors/b77d6be5f3e1f0e6. Report an issue: GitHub.