github/copilot-sdk · error · InvalidOperationException
Unsupported JSON-RPC params shape
Error message
Unsupported JSON-RPC params shape '{paramsProp.ValueKind}' for handler with positional parameters. What it means
InvokeHandlerAsync dispatches JSON-RPC requests to registered handlers. When a handler declares required positional parameters, the request's `params` must be a JSON array (positional) or object (by-name); anything else — including missing/null params or a scalar/string params value — is a protocol violation, so the library throws instead of silently filling in default values.
Solutions
- Include a `params` value in the request matching the handler signature: a JSON array of positional arguments or an object keyed by parameter name.
- If the method truly takes no parameters, register the handler without required positional parameters (e.g. accept a parameterless delegate or use a params object/CancellationToken-only signature).
- Validate the outgoing request on the client side: ensure params is an array or object before sending.
- Catch InvalidOperationException in InvokeHandlerAsync callers and return a JSON-RPC Invalid Params error (-32602) instead of crashing.
Example fix
// before
await rpc.InvokeAsync("add", (object?)null);
// after
await rpc.InvokeAsync("add", new object[] { 1, 2 }); Defensive patterns
Strategy: validation
Validate before calling
if (params is not (JsonElement.ValueKind.Array or JsonElement.ValueKind.Object))
throw new ArgumentException("JSON-RPC params must be an array or object for positional handlers", nameof(params)); Type guard
static bool HasValidParamsShape(JsonElement p) => p.ValueKind is JsonValueKind.Array or JsonValueKind.Object;
Try / catch
try { await rpc.InvokeAsync(method, args); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Unsupported JSON-RPC params shape"))
{ return JsonRpcError.InvalidParams(ex.Message); } Prevention
- Always serialize params as an array or object in JSON-RPC requests
- Validate request shape client-side before sending
- Match handler signatures to the params shape you send
When it happens
Trigger: Sending a JSON-RPC request whose `params` is absent, null, a bare string/number/boolean, while the target handler was registered with required positional parameters (e.g. calling a method with `"params": null` or omitting params entirely).
Common situations: Hand-rolled JSON-RPC clients omitting params for no-arg-looking methods; protocol proxies forwarding notifications; protocol version mismatches where a caller assumes params are optional; malformed requests from LLM/tool harnesses generating raw JSON.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Failed to deserialize permission request
- Expected a string token when reading
- Expected a non-empty string token when reading
- Expected string for ToolBinaryResultType.
- ToolBinaryResultType value cannot be null.
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/f97d31f350cc51ea.
Report an issue: GitHub.
Appendix: source
Thrown at dotnet/src/JsonRpc.cs:680
{
invokeArgs[i] = cancellationToken;
}
else if (parameters[i].Name is { } paramName &&
TryGetPropertyCaseInsensitive(paramsProp, paramName, out var valueProp))
{
invokeArgs[i] = valueProp.Deserialize(_serializerOptions.GetTypeInfo(parameters[i].ParameterType));
}
else
{
invokeArgs[i] = parameters[i].HasDefaultValue ? parameters[i].DefaultValue : null;
}
}
}
else
{
// Missing/null `params` for a handler with required positional parameters is a
// protocol violation. Surface it as an error rather than silently filling defaults.
throw new InvalidOperationException(
$"Unsupported JSON-RPC params shape '{paramsProp.ValueKind}' for handler with positional parameters.");
}
// Invoke
var result = registration.Handler.DynamicInvoke(invokeArgs);
// Handlers return one of: a synchronous value, Task (void async), or ValueTask<T>.
if (result is Task task)
{
// Task<T> handlers are not supported — use ValueTask<T> for results.
Debug.Assert(!task.GetType().IsGenericType, "Task<T> handlers are not supported; use ValueTask<T>.");
await task.ConfigureAwait(false);
return null;
}
if (result is not null && registration.ValueTaskAsTaskMethod is { } valueTaskAsTaskMethod)
{
var asTask = (Task)valueTaskAsTaskMethod.Invoke(result, null)!;View on GitHub (pinned to cd8cf15dc3)