can1357/oh-my-pi · error · SyntaxError

Unterminated object

Error message

Unterminated object

What it means

Thrown by RelaxedJson.#object in strict mode when the input ends (this.#i >= this.#n) while still inside an object — the parser reached end-of-input before seeing the closing '}'. In partial (streaming) mode this is not an error; the object is auto-closed with what was parsed so far. In strict mode it throws so truncated JSON never passes as final.

Source

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

		if (c === '"' || c === "'") return this.#string(s.charCodeAt(this.#i));
		const cc = s.charCodeAt(this.#i);
		if (cc === 0x2d /* - */ || cc === 0x2b /* + */ || cc === 0x2e /* . */ || (cc >= 0x30 && cc <= 0x39)) {
			// JS-only NaN / Infinity are deliberately not accepted: a tool must not
			// execute with a non-finite numeric arg; they fall through #number's
			// NaN guard (strict throw / partial rollback) like other bad tokens.
			return this.#number();
		}
		return this.#keyword(allowBareword);
	}

	#object(): Record<string, unknown> {
		this.#i++; // consume {
		const out: Record<string, unknown> = {};
		for (;;) {
			this.#ws();
			if (this.#i >= this.#n) {
				if (this.#partial) return out;
				throw new SyntaxError("Unterminated object");
			}
			const c = this.#s[this.#i];
			if (c === "}") {
				this.#i++;
				return out;
			}
			if (c === ",") {
				// Tolerate leading / doubled / trailing commas.
				this.#i++;
				continue;
			}
			const key = this.#key();
			this.#ws();
			if (this.#i < this.#n && this.#s[this.#i] === ":") {
				this.#i++;
			} else if (this.#partial) {
				return out;
			} else {

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the source string is complete before parsing (check stream end, file size, response length)
  2. For streaming/incomplete input use parseStreamingJson, which auto-closes truncated objects
  3. Re-request or re-read the payload if it was truncated by a provider or transport
  4. Use classifyJsonPrefix: 'prefix' means the input can still be completed, so wait for more data

Example fix

// before
const v = parseJsonWithRepair(`{"path": "x.ts"`); // SyntaxError
// after: tolerate a truncated streaming buffer
const v = parseStreamingJson(`{"path": "x.ts"`); // {path: 'x.ts'}
Defensive patterns

Strategy: validation

Validate before calling

import { classifyJsonPrefix } from "@oh-my-pi/pi-utils/json-parse";
// 'complete' => safe for parseJsonWithRepair; 'prefix' => wait for more data or use parseStreamingJson
const state = classifyJsonPrefix(text);

Type guard

function isCompleteJson(text: string): boolean {
  return classifyJsonPrefix(text) === "complete";
}

Try / catch

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

Prevention

When it happens

Trigger: parseJsonWithRepair with a truncated object: '{"a": 1', '{"a":', or '{'. Any place end-of-input occurs inside an object body when this.#partial is false.

Common situations: LLM tool-call arguments cut off by a token/size limit; reading a file or network response before it fully arrived; log truncation dropping the tail of a JSON payload.

Related errors


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