github/copilot-sdk · error · IllegalArgumentException

fromClass() requires all @CopilotTool methods to be static…

Error message

fromClass() requires all @CopilotTool methods to be static, but found instance methods: + instanceMethods + . Use fromObject(new  + clazz.getSimpleName() + ()) instead.

What it means

ToolDefinition.fromClass() only supports static @CopilotTool methods because it has no object instance to invoke methods on. If any @CopilotTool-annotated method on the class is an instance method, registration cannot proceed and this IllegalArgumentException is thrown listing the offending method names. The library explicitly directs you to use fromObject(new Clazz()) instead, which provides an instance for dispatch.

Solutions

  1. Make every @CopilotTool method static in the class passed to fromClass().
  2. Alternatively register an instance instead: ToolDefinition.fromObject(new SomeClass()), which supports instance methods.
  3. If the methods need injected state, use fromObject with a constructor-initialized instance rather than forcing statics.

Example fix

// before
class SearchTools {
    @CopilotTool(name = "search")
    public String search(String q) { return doSearch(q); } // instance method
}
ToolDefinition.fromClass(SearchTools.class); // throws

// after
class SearchTools {
    @CopilotTool(name = "search")
    public static String search(String q) { return doSearch(q); }
}
ToolDefinition.fromClass(SearchTools.class); // ok
// or: ToolDefinition.fromObject(new SearchTools());
Defensive patterns

Strategy: validation

Validate before calling

boolean ok = Arrays.stream(Tools.class.getDeclaredMethods())
    .filter(m -> m.isAnnotationPresent(com.github.copilot.tool.CopilotTool.class))
    .allMatch(m -> Modifier.isStatic(m.getModifiers()));
if (!ok) throw new IllegalStateException("all @CopilotTool methods must be static for fromClass");

Try / catch

try { defs = ToolDefinition.fromClass(Tools.class); } catch (IllegalArgumentException e) { log.error("tool registration failed: {}", e.getMessage()); throw e; }

Prevention

When it happens

Trigger: Calling ToolDefinition.fromClass(SomeClass.class) where the class declares one or more non-static methods annotated with @CopilotTool (e.g. public List<String> search(...) instead of public static List<String> search(...)).

Common situations: Converting an existing service class to Copilot tools by adding @CopilotTool to existing instance methods without making them static; forgetting the 'static' modifier when writing a new tool class intended for fromClass(); switching registration from fromObject to fromClass for convenience without moving methods to static.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

     *
     * @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.
     *
     * @param value
     *            {@code true} to indicate this tool intentionally overrides a
     *            built-in CLI tool with the same name
     * @return a new {@code ToolDefinition} with the flag applied
     * @since 1.0.6

View on GitHub (pinned to cd8cf15dc3)