github/copilot-sdk · error · IllegalArgumentException
Expected number at position
Error message
Expected number at position <pos>
What it means
The CopilotToolProcessor contains a hand-rolled JSON tokenizer that converts tool-call argument JSON into Java expressions. While parsing a number literal, the first character must be '0' or a digit 1-9; anything else (e.g. a letter, '+', or a bare '-') makes the parser throw this IllegalArgumentException, since JSON grammar disallows numbers beginning any other way.
Solutions
- Fix the JSON so the number starts with a digit 0-9 (use 0.5, not +0.5 or .5).
- Validate/repair the argument JSON with a standard parser (e.g. Jackson) before invoking the tool.
- Catch IllegalArgumentException around tool invocation and surface the position info (pos) to locate the malformed token.
Example fix
// before
{"retries": +3}
// after
{"retries": 3} Defensive patterns
Strategy: validation
Validate before calling
static boolean validNumberStart(String json) {
java.util.regex.Matcher m = java.util.regex.Pattern.compile("\\"-?\\d+(\\.\\d+)?([eE][+-]?\\d+)?\\"").matcher(json);
return m.find();
}
// or simply: try { new com.fasterxml.jackson.databind.ObjectMapper().readTree(args); } catch (Exception e) { /* reject */ } Type guard
static boolean isJsonNumberStart(String s, int i) {
if (i < 0 || i >= s.length()) return false;
char c = s.charAt(i);
return c == '-' || (c >= '0' && c <= '9');
} Try / catch
try {
processor.parse(args);
} catch (IllegalArgumentException e) {
log.warn("Malformed tool args: {}", e.getMessage());
return Result.rejected(args, e.getMessage());
} Prevention
- Run a standard JSON parser over tool arguments before custom processing.
- Never build number literals by string concatenation from user input.
- Log the raw argument string whenever parsing fails so the offending token is visible.
When it happens
Trigger: Passing tool-call arguments where a numeric field starts with an invalid character: e.g. {"count": +1}, {"count": -} (lone minus reaching the number rule via a path that requires this branch), or a non-numeric token like {"count": abc} where a number was expected.
Common situations: LLM-generated tool arguments containing malformed JSON numbers, hand-written tool invocations with leading '+' or stray characters, template substitution leaving placeholder text where a number should be.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- Unexpected trailing content at position
- Unescaped control character at position <pos-1>
- Unterminated string escape at position
- Invalid escape sequence \
- Incomplete Unicode escape at position <pos-2>
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/61dfcb4c2ad238ea.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java:1113
return "(Object) null";
}
throw new IllegalArgumentException("Expected null at position " + pos);
}
private String parseNumber() {
int start = pos;
if (pos < input.length() && input.charAt(pos) == '-') {
pos++;
}
if (pos >= input.length()) {
throw new IllegalArgumentException("Expected number at position " + start);
}
if (input.charAt(pos) == '0') {
pos++;
} else if (isDigitOneToNine(input.charAt(pos))) {
consumeDigits();
} else {
throw new IllegalArgumentException("Expected number at position " + pos);
}
if (pos < input.length() && input.charAt(pos) == '.') {
pos++;
requireDigit("fraction");
consumeDigits();
}
if (pos < input.length() && (input.charAt(pos) == 'e' || input.charAt(pos) == 'E')) {
pos++;
if (pos < input.length() && (input.charAt(pos) == '+' || input.charAt(pos) == '-')) {
pos++;
}
requireDigit("exponent");
consumeDigits();
}
String number = input.substring(start, pos);
try {
new java.math.BigDecimal(number);
} catch (NumberFormatException e) {View on GitHub (pinned to cd8cf15dc3)