{"record":{"id":"9439d1653574b245","repo":"FoundationAgents/MetaGPT","slug":"unterminated-string-starting-at","errorCode":null,"errorMessage":"Unterminated string starting at","messagePattern":"Unterminated string starting at","errorType":"exception","errorClass":"JSONDecodeError","httpStatus":null,"severity":"error","filePath":"metagpt/utils/custom_decoder.py","lineNumber":227,"sourceCode":"        tuple: A tuple containing the decoded string and the index of the character in `s`\n        after the end quote.\n    \"\"\"\n\n    chunks = []\n    _append = chunks.append\n    begin = end - 1\n    if delimiter == '\"':\n        _m = STRINGCHUNK.match\n    elif delimiter == \"'\":\n        _m = STRINGCHUNK_SINGLEQUOTE.match\n    elif delimiter == '\"\"\"':\n        _m = STRINGCHUNK_TRIPLE_DOUBLE_QUOTE.match\n    else:\n        _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:","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/FoundationAgents/MetaGPT/blob/11cdf466d042aece04fc6cfd13b28e1a70341b1f/metagpt/utils/custom_decoder.py#L209-L245","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ns = '{\"desc\": \"unfinished'   # Unterminated string starting at\nobj = decoder.decode(s)\n\n// after\ns = '{\"desc\": \"unfinished\"}'\nobj = decoder.decode(s)","handlingStrategy":"try-catch","validationCode":"def has_balanced_quotes(s: str, delim: str = '\"') -> bool:\n    # crude check: count unescaped delimiters outside the payload\n    import re\n    body = s.strip()\n    hits = re.findall(r'(?<!\\\\)' + re.escape(delim), body)\n    return len(hits) % 2 == 0","typeGuard":null,"tryCatchPattern":"try:\n    obj = decoder.decode(s)\nexcept json.JSONDecodeError as e:\n    if e.msg.startswith('Unterminated string'):\n        s += '\"}'  # close dangling string+object for partial output\n        obj = decoder.decode(s)","preventionTips":["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."],"tags":["json","parsing","truncation","llm-output"],"backgroundTag":null,"analyzedSha":"11cdf466d042aece04fc6cfd13b28e1a70341b1f","analyzedAt":"2026-08-14T23:20:02.994Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}