{"record":{"id":"b8e9733ba5079779","repo":"FoundationAgents/MetaGPT","slug":"invalid-control-character-0-r-at","errorCode":null,"errorMessage":"Invalid control character {0!r} at","messagePattern":"Invalid control character (.+?) at","errorType":"exception","errorClass":"JSONDecodeError","httpStatus":null,"severity":"error","filePath":"metagpt/utils/custom_decoder.py","lineNumber":241,"sourceCode":"        _m = STRINGCHUNK_TRIPLE_SINGLEQUOTE.match\n    while 1:\n        chunk = _m(s, end)\n        if chunk is None:\n            raise JSONDecodeError(\"Unterminated string starting at\", s, begin)\n        end = chunk.end()\n        content, terminator = chunk.groups()\n        # Content is contains zero or more unescaped string characters\n        if content:\n            _append(content)\n        # Terminator is the end of string, a literal control character,\n        # or a backslash denoting that an escape sequence follows\n        if terminator == delimiter:\n            break\n        elif terminator != \"\\\\\":\n            if strict:\n                # msg = \"Invalid control character %r at\" % (terminator,)\n                msg = \"Invalid control character {0!r} at\".format(terminator)\n                raise JSONDecodeError(msg, s, end)\n            else:\n                _append(terminator)\n                continue\n        try:\n            esc = s[end]\n        except IndexError:\n            raise JSONDecodeError(\"Unterminated string starting at\", s, begin) from None\n        # If not a unicode escape sequence, must be in the lookup table\n        if esc != \"u\":\n            try:\n                char = _b[esc]\n            except KeyError:\n                msg = \"Invalid \\\\escape: {0!r}\".format(esc)\n                raise JSONDecodeError(msg, s, end)\n            end += 1\n        else:\n            uni = _decode_uXXXX(s, end)\n            end += 5","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/FoundationAgents/MetaGPT/blob/11cdf466d042aece04fc6cfd13b28e1a70341b1f/metagpt/utils/custom_decoder.py#L223-L259","documentation":"Raised inside py_scanstring when strict=True (the default) and a literal control character (code point < 0x20, e.g. a raw newline or tab) is found inside a JSON string instead of its escaped form (\\n, \\t). Strict JSON forbids raw control characters in strings, and this patched decoder keeps that rule.","triggerScenarios":"Decoding a string value that contains a literal newline or tab character, e.g. '{\"code\": \"line1\\nline2\"}' where \\n is an actual 0x0A byte rather than the two characters backslash-n. Occurs when multi-line code blocks from LLM output are embedded raw into JSON.","commonSituations":"Embedding LLM-generated multi-line code or logs into JSON without json.dumps-style escaping; copy-pasting text with control characters; Windows CRLF artifacts.","solutions":["Escape the control characters before decoding (replace raw \\n with \\\\n, raw \\t with \\\\t), e.g. via json.dumps when building the payload.","Decode with strict=False if you deliberately want to accept raw control characters (the decoder will append them as content).","Sanitize the string: strip or map control characters with str.translate before parsing."],"exampleFix":"// before\ns = '{\"code\": \"def f():\\n    pass\"}'  # raw newline -> Invalid control character '\\\\n'\n\n// after\nimport json\ns = json.dumps({\"code\": \"def f():\\n    pass\"})  # properly escaped: \\\\n\n# or tolerate raw control chars:\ndecoder = JSONDecoder(strict=False)","handlingStrategy":"validation","validationCode":"import re\nCTRL = {c: f'\\\\u{ord(c):04x}' for c in map(chr, range(0x20))}\ndef escape_control_chars(text: str) -> str:\n    return text.translate(CTRL)","typeGuard":null,"tryCatchPattern":"try:\n    obj = decoder.decode(s)\nexcept json.JSONDecodeError as e:\n    if e.msg.startswith('Invalid control character'):\n        obj = decoder.decode(escape_control_chars(s))","preventionTips":["Build JSON with json.dumps (it escapes control characters by default).","Decode with strict=False when raw newlines in strings are acceptable.","Sanitize pasted multi-line text with str.translate before embedding."],"tags":["json","parsing","escaping","llm-output"],"backgroundTag":null,"analyzedSha":"11cdf466d042aece04fc6cfd13b28e1a70341b1f","analyzedAt":"2026-08-14T23:20:02.994Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}