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
- Make every @CopilotTool method static in the class passed to fromClass().
- Alternatively register an instance instead: ToolDefinition.fromObject(new SomeClass()), which supports instance methods.
- 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
- Make @CopilotTool methods static by default when designing fromClass-based tool classes.
- Use fromObject(new Clazz()) whenever handlers need instance state.
- Add an architecture/unit test asserting all @CopilotTool methods in fromClass-registered classes are static.
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
- clazz must not be null
- Failed to invoke + metaClassName + .definitions()
- instance must not be null
- Tool name must not be null or blank
- Tool description must not be null or blank
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.6View on GitHub (pinned to cd8cf15dc3)