github/copilot-sdk · error · IllegalArgumentException

A Param descriptor is null for tool ' + toolName + '

Error message

A Param descriptor is null for tool ' + toolName + '

What it means

Thrown by ParamSchema.buildSchema during validation when the params array passed for a tool contains a null element. buildSchema validates that every Param descriptor is non-null and that names are unique before generating the JSON schema. It is a fail-fast guard against tool registration bugs.

Solutions

  1. Remove null entries before calling buildSchema: params.removeIf(Objects::isNull)
  2. Fix the factory/builder that produced a null Param instead of a descriptor
  3. Assert all params non-null in tool registration tests
  4. Catch IllegalArgumentException and log toolName to identify the misregistered tool

Example fix

// before
List<Param<?>> params = Arrays.asList(p1, null, p3);
ParamSchema.buildSchema("myTool", params); // throws
// after
List<Param<?>> params = Arrays.asList(p1, p3);
ParamSchema.buildSchema("myTool", params);
Defensive patterns

Strategy: validation

Validate before calling

if (params.stream().anyMatch(Objects::isNull)) { throw new IllegalStateException("null Param in " + toolName); }

Type guard

boolean hasNoNullParams(List<Param<?>> ps) { return ps != null && ps.stream().noneMatch(Objects::isNull); }

Try / catch

try { schema = ParamSchema.buildSchema(toolName, params); } catch (IllegalArgumentException e) { throw new ToolRegistrationException(toolName, e); }

Prevention

When it happens

Trigger: Calling buildSchema(toolName, params) with a list containing null — e.g. a Param created conditionally and left null, or arrays assembled with placeholder nulls.

Common situations: Programmatic tool registration where a factory method returns null; refactors that remove a Param but leave a null slot; reflection/annotation-processor gaps producing null descriptors.

Related errors


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

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ParamSchema.java:76

     *            the configured {@link ObjectMapper} used to coerce default values
     *            into their typed form for the schema
     * @param params
     *            zero or more parameter descriptors
     * @return a JSON Schema object map with {@code type=object},
     *         {@code properties}, and {@code required} keys
     * @throws IllegalArgumentException
     *             if a null param or duplicate parameter names are found
     */
    static Map<String, Object> buildSchema(String toolName, ObjectMapper mapper, Param<?>... params) {
        if (params == null || params.length == 0) {
            return Map.of("type", "object", "properties", Map.of(), "required", List.of());
        }

        // Validate: no null params, no duplicate names
        Set<String> seen = new HashSet<>();
        for (Param<?> param : params) {
            if (param == null) {
                throw new IllegalArgumentException("A Param descriptor is null for tool '" + toolName + "'");
            }
            if (!seen.add(param.name())) {
                throw new IllegalArgumentException(
                        "Duplicate parameter name '" + param.name() + "' in tool '" + toolName + "'");
            }
        }

        List<String> requiredNames = new ArrayList<>();
        Map<String, Object> properties = new LinkedHashMap<>();

        for (Param<?> param : params) {
            Map<String, Object> typeSchema;
            if (!param.schema().isEmpty()) {
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> parsed = mapper.readerFor(Map.class)
                            .with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
                            .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS).readValue(param.schema());

View on GitHub (pinned to cd8cf15dc3)