{"record":{"id":"3e0fcf823ac6c1c8","repo":"FoundationAgents/MetaGPT","slug":"header-keys-must-be-strings","errorCode":null,"errorMessage":"Header keys must be strings","messagePattern":"Header keys must be strings","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"metagpt/provider/general_api_base.py","lineNumber":431,"sourceCode":"        if self.api_version is not None and self.api_type == ApiType.OPEN_AI:\n            headers[\"LLM-Version\"] = self.api_version\n        if request_id is not None:\n            headers[\"X-Request-Id\"] = request_id\n        headers.update(extra)\n\n        return headers\n\n    def _validate_headers(self, supplied_headers: Optional[Dict[str, str]]) -> Dict[str, str]:\n        headers: Dict[str, str] = {}\n        if supplied_headers is None:\n            return headers\n\n        if not isinstance(supplied_headers, dict):\n            raise TypeError(\"Headers must be a dictionary\")\n\n        for k, v in supplied_headers.items():\n            if not isinstance(k, str):\n                raise TypeError(\"Header keys must be strings\")\n            if not isinstance(v, str):\n                raise TypeError(\"Header values must be strings\")\n            headers[k] = v\n\n        # NOTE: It is possible to do more validation of the headers, but a request could always\n        # be made to the API manually with invalid headers, so we need to handle them server side.\n\n        return headers\n\n    def _prepare_request_raw(\n        self,\n        url,\n        supplied_headers,\n        method,\n        params,\n        files,\n        request_id: Optional[str],\n    ) -> Tuple[str, Dict[str, str], Optional[bytes]]:","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/FoundationAgents/MetaGPT/blob/11cdf466d042aece04fc6cfd13b28e1a70341b1f/metagpt/provider/general_api_base.py#L413-L449","documentation":"Raised inside _validate_headers while iterating supplied headers: the container is a dict, but at least one key is not a str. Header keys become HTTP header names, so non-string keys (int, tuple, bytes) are rejected client-side before the request is sent.","triggerScenarios":"Passing a dict with non-string keys such as {8080: \"true\"}, {(\"a\", \"b\"): \"v\"}, or {b\"X-Trace\": \"1\"} as default_headers/supplied_headers to any request through the general API base.","commonSituations":"Building headers dynamically from numeric IDs or enum values without str() conversion, mixing Python 2-style bytes keys into a ported codebase, or using dict(zip(range(...), values)) by mistake.","solutions":["Convert all header keys to strings: {str(k): v for k, v in headers.items()}.","For bytes keys, decode first: {k.decode(): v for k, v in headers.items()}.","Replace numeric or tuple keys with the intended header names (e.g. \"X-Request-Id\").","Add a startup assertion that all(isinstance(k, str) for k in headers)."],"exampleFix":"# before\nheaders = {1: \"application/json\"}  # int key -> TypeError\n\n# after\nheaders = {\"1\": \"application/json\"}  # or {str(k): v for k, v in headers.items()}","handlingStrategy":"type-guard","validationCode":"headers = {str(k): v for k, v in raw_headers.items()}\nassert all(isinstance(k, str) for k in headers)","typeGuard":"def has_string_keys(h: Dict[Any, Any]) -> TypeGuard[Dict[str, Any]]:\n    return isinstance(h, dict) and all(isinstance(k, str) for k in h)","tryCatchPattern":"try:\n    validated = requestor._validate_headers(headers)\nexcept TypeError:\n    headers = {str(k): str(v) for k, v in headers.items()}\n    validated = requestor._validate_headers(headers)","preventionTips":["Build headers only from string literals or str() calls","Decode bytes keys before assembling header dicts","Lint for non-literal dict keys in header-building code"],"tags":["http-headers","type-error","validation"],"backgroundTag":null,"analyzedSha":"11cdf466d042aece04fc6cfd13b28e1a70341b1f","analyzedAt":"2026-08-14T23:20:02.994Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}