github/copilot-sdk · error · IllegalArgumentException

Required parameter ' + param.name() + ' is missing from…

Error message

Required parameter ' + param.name() + ' is missing from tool invocation

What it means

ParamCoercion.coerce converts raw JSON tool-invocation arguments into typed Java parameters. When a raw argument is null and the ToolParameter is marked required (with no default value), the library throws IllegalArgumentException because a required tool parameter was not supplied by the model/caller.

Solutions

  1. Mark the parameter as required in the tool's JSON Schema ("required": [name]) so the model is forced to supply it.
  2. Give the parameter a default value via hasDefaultValue/coerceDefault if it can reasonably be defaulted.
  3. Validate the arguments map against the tool's parameter list before dispatching and return a clear tool error to the model.
  4. If the parameter should be optional, change required=false or use Optional/nullable types so emptyOptionalOrNull is returned instead.

Example fix

// before
@ToolParameter(name = "path", required = true)
// schema generated without required -> model omits it

// after
// ensure generated schema includes: {"type":"object","required":["path"],...}
// and/or provide a default:
@ToolParameter(name = "path", required = true, defaultValue = ".")
Defensive patterns

Strategy: validation

Validate before calling

for (ToolParameter p : tool.parameters()) {
    if (p.required() && !p.hasDefaultValue() && !args.containsKey(p.name())) {
        throw new ToolInvocationException("missing required argument: " + p.name());
    }
}

Type guard

static boolean hasRequiredArgs(Map<String,Object> args, List<ToolParameter> params) {
    return params.stream()
        .filter(ToolParameter::required)
        .filter(p -> !p.hasDefaultValue())
        .allMatch(p -> args.get(p.name()) != null);
}

Try / catch

try {
    Object v = ParamCoercion.coerce(param, raw, mapper);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("is missing from tool invocation")) {
        return toolError("missing required parameter: " + param.name());
    }
    throw e;
}

Prevention

When it happens

Trigger: Invoking a registered tool where a @ToolParameter(required=true) argument is absent from the arguments object or explicitly null — e.g. the LLM omitted the key or sent {"path": null}.

Common situations: Models omitting optional-seeming arguments the developer marked required; schema/JSON-Schema not declaring the parameter required so the model drops it; callers invoking tools programmatically with incomplete maps; renaming a parameter so the old key no longer matches.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ParamCoercion.java:70

     * @param param
     *            the parameter descriptor
     * @param mapper
     *            the configured {@link ObjectMapper} for complex type conversion
     * @return the coerced argument value
     * @throws IllegalArgumentException
     *             if a required parameter is missing or coercion fails
     */
    @SuppressWarnings("unchecked")
    static <T> T coerce(Map<String, Object> args, Param<T> param, ObjectMapper mapper) {
        Object raw = (args != null) ? args.get(param.name()) : null;

        if (raw == null) {
            if (param.hasDefaultValue()) {
                return coerceDefault(param, mapper);
            } else if (!param.required()) {
                return (T) emptyOptionalOrNull(param.type());
            } else {
                throw new IllegalArgumentException(
                        "Required parameter '" + param.name() + "' is missing from tool invocation");
            }
        }

        Class<T> type = param.type();

        // Handle Optional* types explicitly before delegating to ObjectMapper
        if (type == java.util.OptionalInt.class) {
            try {
                return (T) java.util.OptionalInt.of(((Number) raw).intValue());
            } catch (ClassCastException ex) {
                throw new IllegalArgumentException("Parameter '" + param.name()
                        + "' expected a numeric value for OptionalInt, got: " + raw.getClass().getSimpleName(), ex);
            }
        }
        if (type == java.util.OptionalLong.class) {
            try {
                return (T) java.util.OptionalLong.of(((Number) raw).longValue());

View on GitHub (pinned to cd8cf15dc3)