can1357/oh-my-pi · error · SyntaxError

Unterminated string

Error message

Unterminated string

What it means

Thrown by RelaxedJson.#string in strict mode when end-of-input is reached without a closing quote. In partial mode an unterminated string is returned as-is (streaming auto-close); in strict mode it throws. Note the parser does try to recover unescaped inner quotes ('it's'), so this error means the string genuinely never terminates.

Source

Thrown at packages/utils/src/json-parse.ts:442

						out += String.fromCharCode(parseInt(hex, 16));
						i += 4;
					} else {
						out += "\\u"; // invalid \u — keep literal
					}
					break;
				}
				default:
					out += `\\${s[i]}`; // invalid escape — keep backslash literal
			}
			i++;
			runStart = i;
		}
		out += s.slice(runStart, i);
		if (this.#partial) {
			this.#i = i;
			return out;
		}
		throw new SyntaxError("Unterminated string");
	}

	/** A quote closes a string only when the next non-space char ends a value. */
	#closesString(from: number): boolean {
		const s = this.#s;
		let k = from;
		while (k < this.#n && isWhitespace(s.charCodeAt(k))) k++;
		if (k >= this.#n) return true;
		const c = s[k];
		return c === "," || c === "}" || c === "]" || c === ":";
	}

	#number(): unknown {
		const s = this.#s;
		const start = this.#i;
		while (this.#i < this.#n) {
			const ch = s[this.#i];
			if (

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the complete payload arrives before final parsing (check stream completion / content-length)
  2. Use parseStreamingJson for incomplete input — unterminated strings are auto-closed
  3. Use classifyJsonPrefix: a 'prefix' result means the string may still be closed by more data
  4. Re-generate or re-request the output if a provider truncated it

Example fix

// before
const v = parseJsonWithRepair(`{"msg": "hello`); // SyntaxError: Unterminated string
// after
const v = parseStreamingJson(`{"msg": "hello`); // {msg: 'hello'}
Defensive patterns

Strategy: validation

Validate before calling

import { classifyJsonPrefix } from "@oh-my-pi/pi-utils/json-parse";
// 'prefix' includes unterminated strings that more data may close; 'invalid' cannot be repaired
const state = classifyJsonPrefix(text);

Try / catch

try {
  const value = parseJsonWithRepair<T>(text);
} catch (err) {
  if (err instanceof SyntaxError && err.message === "Unterminated string") {
    const partial = parseStreamingJson<T>(text); // auto-closes the string
  } else throw err;
}

Prevention

When it happens

Trigger: parseJsonWithRepair with '"abc', '{"key": "value', or a quote whose closer was consumed as an escape target ('abc\\' — trailing backslash swallows the quote, runStart breaks, then EOI). Only in strict mode; parseStreamingJson never throws this.

Common situations: LLM tool-call output truncated mid-string by a token limit; a JSON string missing its closing quote in hand-written config; a payload cut by a fixed-size read or log clipping.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ebd5c4d7735c49e6. Report an issue: GitHub.