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
- Ensure the complete payload arrives before final parsing (check stream completion / content-length)
- Use parseStreamingJson for incomplete input — unterminated strings are auto-closed
- Use classifyJsonPrefix: a 'prefix' result means the string may still be closed by more data
- 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
- Await full stream completion before final-parsing
- Use parseStreamingJson for streaming tool-call argument buffers
- Avoid trailing backslashes in string data (they swallow the closing quote)
- Use classifyJsonPrefix to distinguish still-open strings from dead input
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
- Unterminated object
- Unterminated array
- Expected value after ':'
- Unexpected trailing characters at position ${this.#i}
- Expected ':' in object
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ebd5c4d7735c49e6.
Report an issue: GitHub.