{"record":{"id":"ac7e1dd0086ee943","repo":"github/copilot-sdk","slug":"parameter-param-name-expected-a-numeric","errorCode":null,"errorMessage":"Parameter ' + param.name() + ' expected a numeric value for OptionalInt, got: + raw.getClass().getSimpleName()","messagePattern":"Parameter ' \\+ param\\.name\\(\\) \\+ ' expected a numeric value for OptionalInt, got: \\+ raw\\.getClass\\(\\)\\.getSimpleName\\(\\)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/rpc/ParamCoercion.java","lineNumber":82,"sourceCode":"        if (raw == null) {\n            if (param.hasDefaultValue()) {\n                return coerceDefault(param, mapper);\n            } else if (!param.required()) {\n                return (T) emptyOptionalOrNull(param.type());\n            } else {\n                throw new IllegalArgumentException(\n                        \"Required parameter '\" + param.name() + \"' is missing from tool invocation\");\n            }\n        }\n\n        Class<T> type = param.type();\n\n        // Handle Optional* types explicitly before delegating to ObjectMapper\n        if (type == java.util.OptionalInt.class) {\n            try {\n                return (T) java.util.OptionalInt.of(((Number) raw).intValue());\n            } catch (ClassCastException ex) {\n                throw new IllegalArgumentException(\"Parameter '\" + param.name()\n                        + \"' expected a numeric value for OptionalInt, got: \" + raw.getClass().getSimpleName(), ex);\n            }\n        }\n        if (type == java.util.OptionalLong.class) {\n            try {\n                return (T) java.util.OptionalLong.of(((Number) raw).longValue());\n            } catch (ClassCastException ex) {\n                throw new IllegalArgumentException(\"Parameter '\" + param.name()\n                        + \"' expected a numeric value for OptionalLong, got: \" + raw.getClass().getSimpleName(), ex);\n            }\n        }\n        if (type == java.util.OptionalDouble.class) {\n            try {\n                return (T) java.util.OptionalDouble.of(((Number) raw).doubleValue());\n            } catch (ClassCastException ex) {\n                throw new IllegalArgumentException(\"Parameter '\" + param.name()\n                        + \"' expected a numeric value for OptionalDouble, got: \" + raw.getClass().getSimpleName(), ex);\n            }","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/rpc/ParamCoercion.java#L64-L100","documentation":"ParamCoercion.coerce handles java.util.OptionalInt parameters by casting the raw argument to Number and calling intValue(). If the raw value is not a JSON number (e.g. a string \"42\", boolean, or object), the ClassCastException is rethrown as IllegalArgumentException explaining the parameter needed a numeric value for OptionalInt.","triggerScenarios":"A tool invocation supplies a non-numeric JSON value (string, boolean, array) for a parameter typed OptionalInt — commonly a stringified number like \"42\" produced by the model or by client-side serialization.","commonSituations":"Models quoting numeric arguments; config/CLI inputs passing strings; a schema declaring the parameter as string type while the Java signature uses OptionalInt; JavaScript clients sending numbers as strings.","solutions":["Ensure the tool's JSON Schema declares the parameter as {\"type\":\"integer\"} so models emit real numbers.","Coerce obvious numeric strings yourself before dispatch: if raw is String, parse with Integer.parseInt and pass a Number.","Change the parameter type to Optional<Integer> or String if mixed-type input is expected, and parse manually.","Catch IllegalArgumentException from coerce and return a descriptive tool error so the model retries with a numeric value."],"exampleFix":"// before\nObject raw = args.get(\"count\"); // \"42\" (string)\n// coerce throws: expected a numeric value for OptionalInt\n\n// after\nif (raw instanceof String s) {\n    raw = Integer.valueOf(s.trim());\n}\nObject value = ParamCoercion.coerce(param, raw, mapper);","handlingStrategy":"validation","validationCode":"Object raw = args.get(param.name());\nif (param.type() == OptionalInt.class && raw instanceof String s) {\n    raw = Integer.valueOf(s.trim()); // normalize stringified numbers\n}\nif (raw != null && !(raw instanceof Number)) {\n    throw new ToolInvocationException(param.name() + \" must be an integer\");\n}","typeGuard":"static boolean isNumericArg(Object raw) {\n    return raw instanceof Number ||\n           (raw instanceof String s && s.matches(\"-?\\\\d+\"));\n}","tryCatchPattern":"try {\n    Object v = ParamCoercion.coerce(param, raw, mapper);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"expected a numeric value for OptionalInt\")) {\n        return toolError(param.name() + \" must be an integer, got: \" + raw);\n    }\n    throw e;\n}","preventionTips":["Declare integer parameters as {\"type\":\"integer\"} in the tool schema","Normalize stringified numbers before coercion","Prefer Optional<Integer> when clients may send loosely typed values","Return descriptive tool errors so the model retries with correct types"],"tags":["java","rpc","tool-invocation","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}