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

  1. Add the missing closing delimiter at the position indicated by 'starting at' in the message.
  2. If output was truncated by max_tokens, increase the limit or ask for shorter output and regenerate.
  3. For partial/repaired JSON, append synthetic closing quotes/braces before decoding (this is what repair_partial_json-style helpers do).
  4. 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

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


AI-assisted analysis of FoundationAgents/MetaGPT@11cdf466d0 (2026-08-14). Data as JSON: /api/errors/9439d1653574b245. Report an issue: GitHub.