github/copilot-sdk · error · IllegalArgumentException
Unescaped control character at position <pos-1>
Error message
Unescaped control character at position <pos-1>
What it means
While scanning a JSON string literal, the converter's string parser encountered a character below 0x20 (a raw control character such as newline, tab, or NUL) that was not escaped with a backslash. Strict JSON forbids literal control characters inside strings; they must be written as escapes (\n, \t, \uXXXX, etc.).
Solutions
- Fix the producer to emit escaped control characters (\n, \t) or use a real JSON serializer instead of string concatenation
- Pre-sanitize the input by replacing raw control chars (c < 0x20) inside strings with their escape sequences before parsing
- Locate the reported position to identify which control character leaked in and where it came from
- If the payload genuinely contains binary/control data, Base64-encode it inside the JSON string
Example fix
// before
String json = "{\"msg\":\"hello\nworld\"}"; // raw newline inside string
// after
String json = "{\"msg\":\"hello\\nworld\"}"; // escaped \n Defensive patterns
Strategy: try-catch
Validate before calling
for (int i = 0; i < json.length(); i++) {
if (json.charAt(i) < 0x20 && json.charAt(i) != '\n' /* inside strings this is still invalid */) {
throw new IllegalArgumentException("Raw control character at index " + i);
}
} Try / catch
try {
String result = CopilotToolProcessor.jsonToMapOfSource(json);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Unescaped control character")) {
// re-serialize the payload with a proper JSON encoder and retry
}
} Prevention
- Always build JSON with a serializer, never string concatenation
- Base64-encode binary or control-heavy data before embedding in JSON strings
- Strip ANSI/terminal escape codes from data captured from logs
- Validate payloads from external sources with a strict JSON parser first
When it happens
Trigger: Parsing JSON whose string value contains a raw newline, tab, carriage return, or other control char, e.g. '{"text":"line1 line2"}' produced by naive string concatenation instead of proper JSON serialization.
Common situations: Hand-built JSON via string concatenation with real newlines/tabs inside string values; data copied from logs or terminals embedding control bytes; binary data or terminal escape codes (ANSI color) pasted into JSON strings.
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
- Unterminated string escape at position
- Invalid escape sequence \
- Incomplete Unicode escape at position <pos-2>
- Invalid Unicode escape at position <pos-1>
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/c6551a0ff43315f1.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java:1033
if (c == 't' || c == 'f') {
return parseBoolean();
}
if (c == 'n') {
return parseNull();
}
return parseNumber();
}
private String parseString() {
expect('"');
StringBuilder sb = new StringBuilder();
while (pos < input.length() && input.charAt(pos) != '"') {
char current = input.charAt(pos++);
if (current == '\\') {
sb.append(parseEscape());
} else {
if (current < 0x20) {
throw new IllegalArgumentException("Unescaped control character at position " + (pos - 1));
}
sb.append(current);
}
}
expect('"');
return sb.toString();
}
private char parseEscape() {
if (pos >= input.length()) {
throw new IllegalArgumentException("Unterminated string escape at position " + pos);
}
char escaped = input.charAt(pos++);
return switch (escaped) {
case '"', '\\', '/' -> escaped;
case 'b' -> '\b';
case 'f' -> '\f';
case 'n' -> '\n';View on GitHub (pinned to cd8cf15dc3)