FoundationAgents/MetaGPT · error · JSONDecodeError
Invalid control character {0!r} at
Error message
Invalid control character {0!r} at What it means
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.
Source
Thrown at metagpt/utils/custom_decoder.py:241
_m = STRINGCHUNK_TRIPLE_SINGLEQUOTE.match
while 1:
chunk = _m(s, end)
if chunk is None:
raise JSONDecodeError("Unterminated string starting at", s, begin)
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
if content:
_append(content)
# Terminator is the end of string, a literal control character,
# or a backslash denoting that an escape sequence follows
if terminator == delimiter:
break
elif terminator != "\\":
if strict:
# msg = "Invalid control character %r at" % (terminator,)
msg = "Invalid control character {0!r} at".format(terminator)
raise JSONDecodeError(msg, s, end)
else:
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise JSONDecodeError("Unterminated string starting at", s, begin) from None
# If not a unicode escape sequence, must be in the lookup table
if esc != "u":
try:
char = _b[esc]
except KeyError:
msg = "Invalid \\escape: {0!r}".format(esc)
raise JSONDecodeError(msg, s, end)
end += 1
else:
uni = _decode_uXXXX(s, end)
end += 5View on GitHub (pinned to 11cdf466d0)
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.
Example fix
// before
s = '{"code": "def f():\n pass"}' # raw newline -> Invalid control character '\\n'
// after
import json
s = json.dumps({"code": "def f():\n pass"}) # properly escaped: \\n
# or tolerate raw control chars:
decoder = JSONDecoder(strict=False) Defensive patterns
Strategy: validation
Validate before calling
import re
CTRL = {c: f'\\u{ord(c):04x}' for c in map(chr, range(0x20))}
def escape_control_chars(text: str) -> str:
return text.translate(CTRL) Try / catch
try:
obj = decoder.decode(s)
except json.JSONDecodeError as e:
if e.msg.startswith('Invalid control character'):
obj = decoder.decode(escape_control_chars(s)) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Embedding LLM-generated multi-line code or logs into JSON without json.dumps-style escaping; copy-pasting text with control characters; Windows CRLF artifacts.
Related errors
- Expecting property name enclosed in double quotes
- Expecting ':' delimiter
- Expecting value
- Unterminated string starting at
- Invalid \\escape: {0!r}
AI-assisted analysis of FoundationAgents/MetaGPT@11cdf466d0 (2026-08-14).
Data as JSON: /api/errors/b8e9733ba5079779.
Report an issue: GitHub.