FoundationAgents/MetaGPT · error · JSONDecodeError
Unterminated string starting at
Error message
Unterminated string starting at
What it means
Raised inside py_scanstring of MetaGPT's custom decoder: the STRINGCHUNK regex (selected by the delimiter: ", ', triple-double or triple-single quote) stopped matching before a closing delimiter was found, so the string never terminates within the remaining input.
Source
Thrown at metagpt/utils/custom_decoder.py:227
tuple: A tuple containing the decoded string and the index of the character in `s`
after the end quote.
"""
chunks = []
_append = chunks.append
begin = end - 1
if delimiter == '"':
_m = STRINGCHUNK.match
elif delimiter == "'":
_m = STRINGCHUNK_SINGLEQUOTE.match
elif delimiter == '"""':
_m = STRINGCHUNK_TRIPLE_DOUBLE_QUOTE.match
else:
_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:View on GitHub (pinned to 11cdf466d0)
Solutions
- Add the missing closing delimiter at the position indicated by 'starting at' in the message.
- If output was truncated by max_tokens, increase the limit or ask for shorter output and regenerate.
- For partial/repaired JSON, append synthetic closing quotes/braces before decoding (this is what repair_partial_json-style helpers do).
- Escape inner quotes (\") so the intended terminator is the real end of the string.
Example fix
// before
s = '{"desc": "unfinished' # Unterminated string starting at
obj = decoder.decode(s)
// after
s = '{"desc": "unfinished"}'
obj = decoder.decode(s) Defensive patterns
Strategy: try-catch
Validate before calling
def has_balanced_quotes(s: str, delim: str = '"') -> bool:
# crude check: count unescaped delimiters outside the payload
import re
body = s.strip()
hits = re.findall(r'(?<!\\)' + re.escape(delim), body)
return len(hits) % 2 == 0 Try / catch
try:
obj = decoder.decode(s)
except json.JSONDecodeError as e:
if e.msg.startswith('Unterminated string'):
s += '"}' # close dangling string+object for partial output
obj = decoder.decode(s) Prevention
- Raise max_tokens / request shorter LLM replies to avoid mid-string truncation.
- Detect truncation (finish_reason == 'length') before parsing.
- Escape inner quotes so the intended terminator is unambiguous.
When it happens
Trigger: Decoding text with an unclosed string, e.g. {"a": "hello} , a string ending at end-of-input without its closing quote, or triple-quoted content ('"""...') whose closing triple quote is missing. The delimiter choice (e.g. '"""') that does not appear later in the text also triggers it.
Common situations: Truncated LLM output cut off mid-string (very common with max_tokens limits), unescaped inner quote that ends the chunk early, or expecting triple-quote support when the content only has single/double quotes.
Related errors
- Cannot find the answer phrase "{response}"
- Could not find content between [{tag}] and [/{tag}]
- Expecting property name enclosed in double quotes
- Expecting ':' delimiter
- Expecting value
AI-assisted analysis of FoundationAgents/MetaGPT@11cdf466d0 (2026-08-14).
Data as JSON: /api/errors/9439d1653574b245.
Report an issue: GitHub.