{"record":{"id":"70fa87e550f955db","repo":"github/copilot-sdk","slug":"request-handler-must-return-a-json-serializable-va","errorCode":null,"errorMessage":"Request handler must return a JSON-serializable value, got {type(outcome).__name__}","messagePattern":"Request handler must return a JSON-serializable value, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/copilot/_jsonrpc.py","lineNumber":482,"sourceCode":"\n            async def _await_outcome():\n                try:\n                    await outcome\n                except Exception:  # pylint: disable=broad-except\n                    logger.warning(\"Notification handler raised\", exc_info=True)\n\n            asyncio.create_task(_await_outcome())\n\n    async def _dispatch_request(self, message: dict, handler: RequestHandler):\n        try:\n            params = message.get(\"params\", {})\n            outcome = handler(params)\n            if inspect.isawaitable(outcome):\n                outcome = await outcome\n            if outcome is not None and not isinstance(\n                outcome, dict | list | str | int | float | bool\n            ):\n                raise ValueError(\n                    \"Request handler must return a JSON-serializable value, \"\n                    f\"got {type(outcome).__name__}\"\n                )\n            await self._send_response(message[\"id\"], outcome)\n        except JsonRpcError as exc:\n            logger.debug(\n                \"Error handling JSON-RPC method %s: %s\", message.get(\"method\", \"\"), exc.message\n            )\n            await self._send_error_response(message[\"id\"], exc.code, exc.message, exc.data)\n        except Exception as exc:  # pylint: disable=broad-except\n            logger.debug(\n                \"Error handling JSON-RPC method %s: %s\",\n                message.get(\"method\", \"\"),\n                str(exc),\n                exc_info=True,\n            )\n            await self._send_error_response(message[\"id\"], -32603, str(exc), None)\n","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/python/copilot/_jsonrpc.py#L464-L500","documentation":"The JSON-RPC dispatcher validates that every request handler returns a JSON-serializable value (dict, list, str, int, float, bool) or None. Returning None is treated as a valid empty response, but any other object (custom class, dataclass, set, datetime) is rejected before the response is sent. This guard prevents emitting responses that cannot be serialized into a JSON-RPC result.","triggerScenarios":"A handler registered for a JSON-RPC method returns an object that is not one of dict | list | str | int | float | bool and is not None, e.g. returning a dataclass, set, tuple, datetime, or None-wrapped custom type from `handler(params)` in _dispatch_request.","commonSituations":"Developers register a handler that returns a dataclass or ORM model directly, return a tuple instead of a list, or return a custom result wrapper object; Python allows any return value so this only surfaces at dispatch time.","solutions":["Make the handler return only dict/list/str/int/float/bool/None values","Convert custom objects before returning: call `.model_dump()`, `.to_dict()`, or wrap a tuple in list()","If no response body is needed, return None explicitly (accepted) instead of a sentinel object","Add a return-type annotation/adapter layer on handler registration so conversion happens centrally"],"exampleFix":"// before\ndef handle_status(params):\n    return SessionStatus(active=True)  # dataclass\n\n// after\ndef handle_status(params):\n    return {\"active\": True, \"state\": SessionStatus(params).state}","handlingStrategy":"validation","validationCode":"def is_json_safe(value):\n    return value is None or isinstance(value, (dict, list, str, int, float, bool))\n\nassert is_json_safe(my_handler(params)), \"handler must return JSON-serializable value\"","typeGuard":"def is_json_safe(value) -> bool:\n    return value is None or isinstance(value, (dict, list, str, int, float, bool))","tryCatchPattern":"try:\n    result = await client.request(\"my/method\", params)\nexcept ValueError as e:\n    if \"JSON-serializable\" in str(e):\n        result = coerce_to_json(handler(params))","preventionTips":["Return only dicts/primitives from JSON-RPC handlers","Call .model_dump()/.to_dict() on dataclasses and models before returning","Annotate handlers with -> dict | list | str | int | float | bool | None and enforce with a type checker","Add a unit test per handler asserting the return value passes json.dumps"],"tags":["json-rpc","python","serialization","handler"],"backgroundTag":"json-serialization-failed","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}