{"record":{"id":"94a847991ede9dfc","repo":"can1357/oh-my-pi","slug":"unterminated-array","errorCode":null,"errorMessage":"Unterminated array","messagePattern":"Unterminated array","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/json-parse.ts","lineNumber":310,"sourceCode":"\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (d === \"}\") {\n\t\t\t\tthis.#i++;\n\t\t\t\treturn out;\n\t\t\t}\n\t\t\tif (this.#partial) return out;\n\t\t\tthrow new SyntaxError(\"Expected ',' or '}' in object\");\n\t\t}\n\t}\n\n\t#array(): unknown[] {\n\t\tthis.#i++; // consume [\n\t\tconst out: unknown[] = [];\n\t\tfor (;;) {\n\t\t\tthis.#ws();\n\t\t\tif (this.#i >= this.#n) {\n\t\t\t\tif (this.#partial) return out;\n\t\t\t\tthrow new SyntaxError(\"Unterminated array\");\n\t\t\t}\n\t\t\tconst c = this.#s[this.#i];\n\t\t\tif (c === \"]\") {\n\t\t\t\tthis.#i++;\n\t\t\t\treturn out;\n\t\t\t}\n\t\t\tif (c === \",\") {\n\t\t\t\tthis.#i++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst value = this.#value(true);\n\t\t\tif (value === INCOMPLETE) return out;\n\t\t\tout.push(value);\n\t\t\tthis.#ws();\n\t\t\tconst d = this.#i < this.#n ? this.#s[this.#i] : \"\";\n\t\t\tif (d === \",\") {\n\t\t\t\tthis.#i++;\n\t\t\t\tcontinue;","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/json-parse.ts#L292-L328","documentation":"Thrown by RelaxedJson.#array in strict mode when end-of-input is reached while still inside an array — no closing ']' was found. In partial (streaming) mode the array is auto-closed with the elements parsed so far; in strict mode it throws so truncated JSON fails loudly.","triggerScenarios":"parseJsonWithRepair with '[1, 2', '[', or '[1,' — any input ending inside an array body when this.#partial is false.","commonSituations":"Truncated LLM tool-call arguments (token limit); a file or HTTP body read incompletely; log truncation cutting the tail of the payload.","solutions":["Read/await the complete payload before parsing","Use parseStreamingJson for incomplete streaming input — it returns the array parsed so far","Use classifyJsonPrefix to confirm the buffer is a 'prefix' and wait for more data instead of final-parsing","Re-fetch the payload if the transport truncated it"],"exampleFix":"// before\nconst v = parseJsonWithRepair<number[]>(`[1, 2`); // SyntaxError\n// after\nconst v = parseStreamingJson<number[]>(`[1, 2`); // [1, 2]","handlingStrategy":"validation","validationCode":"import { classifyJsonPrefix } from \"@oh-my-pi/pi-utils/json-parse\";\nif (classifyJsonPrefix(text) === \"complete\") {\n  // safe to final-parse; 'prefix' means the array may still be receiving elements\n}","typeGuard":null,"tryCatchPattern":"try {\n  const value = parseJsonWithRepair<T>(text);\n} catch (err) {\n  if (err instanceof SyntaxError && err.message === \"Unterminated array\") {\n    const partial = parseStreamingJson<T>(text); // auto-closed array so far\n  } else throw err;\n}","preventionTips":["Final-parse only after the producer signals completion","Use parseStreamingJson for incomplete/streaming buffers","Verify the full payload was read (stream end, byte counts)","Use classifyJsonPrefix to decide wait-vs-fail"],"tags":["json","parse","array","unterminated","truncated"],"backgroundTag":"json-unterminated-array","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}