Why it happens
Chrome and Node.js throw Unterminated string in JSON at position N when the text ends before a string’s closing quote. V8 only notices at the very end, so N is always the length of the text: it tells you how much arrived, not where the string opened. A round N such as 4096, 8192 or 65536 points at a fixed limit upstream, like a buffer, a stream chunk, a column or a log line.
1. Cut-off output. An LLM reply that hit its token limit, or a value stored in a field shorter than the JSON. The sample above stops in the middle of description, where the model ran out of tokens.
2. Parsing a stream chunk by chunk. A chunk boundary can fall inside a string. Where it falls decides the message, so the same bug also produces Expected double-quoted property name or Unexpected end of JSON input.
3. A backslash before the closing quote. \" is an escaped quote, so the string runs on to the end of the text. Windows paths and regex patterns pasted into JSON by hand do this:
{"dir": "C:\temp\"}
4. A missing quote in hand-written JSON. ["draft", "final] fails at position 17, its length.
An unescaped quote inside a value gives a different error. In {"title": "The "Best" Pizza"} the string ends at the second quote, and Chrome reports Expected ',' or '}' after property value in JSON at position 16 (line 1 column 17).
How to fix Unterminated string in JSON
LLM output
Check why the model stopped before parsing. OpenAI’s Chat Completions API sets finish_reason to "length" and Anthropic’s Messages API sets stop_reason to "max_tokens" when output hit the limit:
const choice = completion.choices[0];
if (choice.finish_reason === 'length') {
throw new Error('Output hit max_tokens, so the JSON is incomplete');
}
const data = JSON.parse(choice.message.content);
Raise the limit, ask for shorter fields, or use the provider’s structured output mode. To keep what arrived, paste it into JSON Repair: for the sample it closes the string and the object, and description ends at “a sketchbook or a”.
Streams
Parse complete messages only. For newline-delimited JSON, keep the unfinished last line in a buffer:
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
const lines = buffer.split('\n');
buffer = lines.pop(); // the last line may still be incomplete
for (const line of lines) if (line.trim()) handle(JSON.parse(line));
}
if (buffer.trim()) handle(JSON.parse(buffer));
For a single JSON document, collect every chunk and call JSON.parse once, after the stream ends.
JSON built by hand
Let JSON.stringify escape backslashes and quotes instead of writing the text with a template string:
const dir = 'C:\\temp\\'; // C:\temp\
const bad = `{"dir": "${dir}"}`; // {"dir": "C:\temp\"}, unterminated
const good = JSON.stringify({ dir }); // {"dir":"C:\\temp\\"}
Do not run Repair on this case: it reads \t as a tab and \" as a quote, which gives valid JSON with the wrong value. Fix the producer, then check the result in the JSON validator.
Same error in other languages
- Go:
encoding/jsonreports every early end, inside a string or not, asunexpected end of JSON input. - A raw line break inside a string: Chrome reports Bad control character in string literal, but Safari and Bun call it
Unterminated stringtoo.
More messages by engine: common JSON errors.