github/copilot-sdk · error · IllegalArgumentException

clazz must not be null

Error message

clazz must not be null

What it means

Thrown by ToolDefinition.fromClass when the clazz argument is null. fromClass scans a class's declared methods for @CopilotTool annotations (without needing an instance); a null Class gives nothing to scan, so it is rejected with this IllegalArgumentException. Note fromClass also rejects classes mixing static and instance @CopilotTool methods.

Solutions

  1. Verify the class exists and loads before calling: Class.forName(name) with exception handling, then pass its non-null result
  2. Null-check the Class with Objects.requireNonNull(clazz) at the call site
  3. Fix configuration/classpath so the tool class is present
  4. Catch IllegalArgumentException at startup and report which configured class failed to register

Example fix

// before
Class<?> c = Class.forName(cfg.toolClass); // throws elsewhere or yields null path
ToolDefinition.fromClass(c);
// after
Class<?> c = Class.forName(cfg.toolClass); // let CNFE surface here
Objects.requireNonNull(c);
ToolDefinition.fromClass(c);
Defensive patterns

Strategy: validation

Validate before calling

Objects.requireNonNull(clazz, "tool class must not be null"); Class.forName(clazz.getName());

Type guard

boolean canScan(Class<?> c) { return c != null; }

Try / catch

try { defs = ToolDefinition.fromClass(clazz); } catch (IllegalArgumentException e) { throw new ToolRegistrationException(String.valueOf(clazz), e); }

Prevention

When it happens

Trigger: Calling ToolDefinition.fromClass(null) — e.g. Class.forName failed earlier and its result (or a caught ClassNotFoundException path) left the reference null, or a config-specified class name resolved to nothing.

Common situations: Config-driven tool registration where the class name string is wrong or the class isn't on the classpath; refactors deleting the tool class while registration entries remain.

Related errors


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

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java:314

    /**
     * Discovers tool definitions from a class with static
     * {@code @CopilotTool}-annotated methods. Requires that the
     * {@code CopilotToolProcessor} annotation processor ran at compile time
     * (generating the {@code $$CopilotToolMeta} companion class).
     *
     * @param clazz
     *            the class containing static {@code @CopilotTool}-annotated methods
     * @return list of tool definitions with working invocation handlers
     * @throws IllegalStateException
     *             if the generated {@code $$CopilotToolMeta} class is not found
     *             (annotation processor did not run)
     * @since 1.0.6
     */
    @CopilotExperimental
    public static List<ToolDefinition> fromClass(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("clazz must not be null");
        }
        List<String> instanceMethods = Arrays.stream(clazz.getDeclaredMethods())
                .filter(m -> m.isAnnotationPresent(com.github.copilot.tool.CopilotTool.class))
                .filter(m -> !Modifier.isStatic(m.getModifiers())).map(Method::getName).collect(Collectors.toList());
        if (!instanceMethods.isEmpty()) {
            throw new IllegalArgumentException(
                    "fromClass() requires all @CopilotTool methods to be static, but found instance methods: "
                            + instanceMethods + ". Use fromObject(new " + clazz.getSimpleName() + "()) instead.");
        }
        return loadDefinitions(clazz, null);
    }

    // ------------------------------------------------------------------
    // Fluent copy-style modifier methods for lambda-defined tools
    // ------------------------------------------------------------------

    /**
     * Returns a copy with the {@code overridesBuiltInTool} flag set.

View on GitHub (pinned to cd8cf15dc3)