github/copilot-sdk · error · ValueError
Request handler must return a JSON-serializable value, got
Error message
Request handler must return a JSON-serializable value, got {type(outcome).__name__} What it means
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.
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
Example fix
// before
def handle_status(params):
return SessionStatus(active=True) # dataclass
// after
def handle_status(params):
return {"active": True, "state": SessionStatus(params).state} Defensive patterns
Strategy: validation
Validate before calling
def is_json_safe(value):
return value is None or isinstance(value, (dict, list, str, int, float, bool))
assert is_json_safe(my_handler(params)), "handler must return JSON-serializable value" Type guard
def is_json_safe(value) -> bool:
return value is None or isinstance(value, (dict, list, str, int, float, bool)) Try / catch
try:
result = await client.request("my/method", params)
except ValueError as e:
if "JSON-serializable" in str(e):
result = coerce_to_json(handler(params)) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- -32603
- -32603
- Failed to detach session
- Failed to serialize tool result
- Invalid entry '*': there is no bare wildcard. Use one or…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/70fa87e550f955db.
Report an issue: GitHub.
Appendix: source
Thrown at python/copilot/_jsonrpc.py:482
async def _await_outcome():
try:
await outcome
except Exception: # pylint: disable=broad-except
logger.warning("Notification handler raised", exc_info=True)
asyncio.create_task(_await_outcome())
async def _dispatch_request(self, message: dict, handler: RequestHandler):
try:
params = message.get("params", {})
outcome = handler(params)
if inspect.isawaitable(outcome):
outcome = await outcome
if outcome is not None and not isinstance(
outcome, dict | list | str | int | float | bool
):
raise ValueError(
"Request handler must return a JSON-serializable value, "
f"got {type(outcome).__name__}"
)
await self._send_response(message["id"], outcome)
except JsonRpcError as exc:
logger.debug(
"Error handling JSON-RPC method %s: %s", message.get("method", ""), exc.message
)
await self._send_error_response(message["id"], exc.code, exc.message, exc.data)
except Exception as exc: # pylint: disable=broad-except
logger.debug(
"Error handling JSON-RPC method %s: %s",
message.get("method", ""),
str(exc),
exc_info=True,
)
await self._send_error_response(message["id"], -32603, str(exc), None)
View on GitHub (pinned to cd8cf15dc3)