github/copilot-sdk · error · IllegalArgumentException

Failed to deserialize arguments to + type.getName()

Error message

Failed to deserialize arguments to  + type.getName()

What it means

ToolInvocation.getArgumentsAs(type) deserializes the tool's JSON arguments node into the requested type via Jackson's treeToValue. If the arguments JSON does not map onto the target type (missing/extra incompatible fields, wrong shapes, malformed data, or null argumentsNode), the exception is wrapped in an IllegalArgumentException with this message naming the target type.

Solutions

  1. Inspect the cause (JsonProcessingException/JsonMappingException) to see which field/path failed to map.
  2. Use readTree/getArgumentsNode to validate the expected fields exist and have the right types before calling getArgumentsAs.
  3. Align the tool's parameter schema (registered in ToolDefinition) with the target DTO's fields and types.
  4. Make the DTO Jackson-friendly: default constructor (or @JsonCreator), correct field names, tolerant config (@JsonIgnoreProperties(ignoreUnknown=true)).

Example fix

// before
SearchArgs a = invocation.getArgumentsAs(SearchArgs.class); // fails on unexpected field

// after
@JsonIgnoreProperties(ignoreUnknown = true)
class SearchArgs { public String query; }
SearchArgs a = invocation.getArgumentsAs(SearchArgs.class);
Defensive patterns

Strategy: validation

Validate before calling

if (invocation.getArgumentsNode() == null) throw new IllegalStateException("no arguments provided");
JsonNode n = invocation.getArgumentsNode();
if (!n.hasNonNull("query")) throw new IllegalArgumentException("missing 'query' argument");

Type guard

static <T> T safeArgs(ToolInvocation inv, Class<T> type) {
  try { return inv.getArgumentsAs(type); }
  catch (IllegalArgumentException e) { log.warn("bad arguments", e.getCause()); return null; }
}

Try / catch

try { SearchArgs a = invocation.getArgumentsAs(SearchArgs.class); }
catch (IllegalArgumentException e) {
  log.error("argument deserialization failed: {}", e.getCause().getMessage());
  return ToolResultObject.of("error", "invalid arguments");
}

Prevention

When it happens

Trigger: Calling invocation.getArgumentsAs(MyDto.class) where the client-sent arguments don't match MyDto (e.g. string where an int is expected, nested object vs scalar), argumentsNode is null, or the DTO has incompatible Jackson expectations (unknown properties without FAIL_ON_UNKNOWN_PROPERTIES disabled, missing no-arg constructor).

Common situations: LLM sending slightly wrong argument shapes (common with loosely-specified schemas); renaming DTO fields after the tool schema was published; deserializing into records/classes lacking Jackson-compatible constructors; invoking the method before arguments were set.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/4ab83763757d00e7. Report an issue: GitHub.

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ToolInvocation.java:161

     * // In your tool handler
     * WeatherArgs args = invocation.getArgumentsAs(WeatherArgs.class);
     * String city = args.city();
     * }</pre>
     *
     * @param <T>
     *            the type to deserialize to
     * @param type
     *            the class of the target type
     * @return the arguments deserialized as the specified type
     * @throws IllegalArgumentException
     *             if deserialization fails
     * @since 1.0.0
     */
    public <T> T getArgumentsAs(Class<T> type) {
        try {
            return MAPPER.treeToValue(argumentsNode, type);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to deserialize arguments to " + type.getName(), e);
        }
    }

    /**
     * Sets the tool arguments.
     * <p>
     * <strong>Note:</strong> This method is intended for internal SDK use and JSON
     * deserialization. Users typically do not need to call this method directly.
     *
     * @param arguments
     *            the arguments as a JsonNode
     * @return this invocation for method chaining
     */
    @JsonSetter("arguments")
    public ToolInvocation setArguments(JsonNode arguments) {
        this.argumentsNode = arguments;
        return this;
    }

View on GitHub (pinned to cd8cf15dc3)