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

  1. Fix the producer to emit escaped control characters (\n, \t) or use a real JSON serializer instead of string concatenation
  2. Pre-sanitize the input by replacing raw control chars (c < 0x20) inside strings with their escape sequences before parsing
  3. Locate the reported position to identify which control character leaked in and where it came from
  4. 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

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


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)