github/copilot-sdk · error · InvalidOperationException
Expected JSON object for `params` of single-object-param…
Error message
Expected JSON object for `params` of single-object-param handler; got '{paramsProp.ValueKind}'. What it means
InvokeHandlerAsync throws InvalidOperationException when a handler registered as taking a single object parameter receives a `params` value that is not a JSON object (e.g. array, string, or null). These handlers deserialize the entire params object into the first parameter, so a non-object shape cannot be bound.
Solutions
- Send params as a JSON object with the field names the TRequest type expects.
- Omit positional-array params; convert them to named-object params on the client.
- Ensure client and server agree on the method's request schema (update the client SDK).
- For optional params, send an empty object rather than null/omitted params if the handler requires an object.
Example fix
// before
{"jsonrpc":"2.0","id":1,"method":"initialize","params":[]}
// after
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"x","version":"1"}}} Defensive patterns
Strategy: validation
Validate before calling
static JsonNode? ValidateParams(JsonElement? raw) =>
raw is { ValueKind: JsonValueKind.Object } el ? JsonNode.Parse(el.GetRawText())
: throw new ArgumentException("params must be a JSON object for this method"); Type guard
bool IsJsonObjectParams(JsonElement p) => p.ValueKind == JsonValueKind.Object;
Try / catch
try { result = await rpc.InvokeAsync(method, requestObj, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("single-object-param handler")) {
logger.LogError(ex, "Client sent non-object params for {Method}", method);
return JsonRpcError.InvalidParams(ex.Message);
} Prevention
- Send named-object params, never positional arrays, for object-param methods
- Generate clients from the same schema as the server handlers
- Send an empty object when all params are optional
- Add conformance tests covering params shapes per method
When it happens
Trigger: Client sends params as an array (positional style) or omits params (ValueKind Undefined/Null) for a method whose handler is a (TRequest, CancellationToken) single-object handler.
Common situations: Client SDK using positional parameters while server method expects named fields; a request built by hand with "params": [] or no params; protocol mismatch between client and server versions.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Client not started. Call start() first.
- Expected a non-empty string token when reading
- Expected a string token when reading
- Expected string for SystemMessageSection.
- Expected string for ToolBinaryResultType.
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/d9790715d7723176.
Report an issue: GitHub.
Appendix: source
Thrown at dotnet/src/JsonRpc.cs:616
}
}
}
private async ValueTask<object?> InvokeHandlerAsync(MethodRegistration registration, JsonElement paramsProp, CancellationToken cancellationToken)
{
var parameters = registration.Parameters;
// Build argument list
var invokeArgs = new object?[parameters.Length];
if (registration.SingleObjectParam)
{
// Single-object deserialization: entire `params` → first parameter.
// Every singleObjectParam handler has shape (TRequest, CancellationToken),
// so `params` must be a JSON object.
if (paramsProp.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException(
$"Expected JSON object for `params` of single-object-param handler; got '{paramsProp.ValueKind}'.");
}
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType == typeof(CancellationToken))
{
invokeArgs[i] = cancellationToken;
}
else if (i == 0)
{
invokeArgs[i] = paramsProp.Deserialize(_serializerOptions.GetTypeInfo(parameters[i].ParameterType));
}
}
}
else if (paramsProp.ValueKind == JsonValueKind.Array)
{
// Positional parameters. Optional params (with defaults) are filled when absent.View on GitHub (pinned to cd8cf15dc3)