can1357/oh-my-pi · error · SyntaxError

Unexpected token at position ${start}

Error message

Unexpected token at position ${start}

What it means

Thrown inside #bareword during strict-mode recovery of an unquoted string value: while scanning the bareword, the scanner hit a character that is forbidden inside a recoverable bareword — a quote, '{', '[', or a ':' not followed by '/' or '\\'. Such a character means the text is not a plausible unquoted string (it looks like real JSON structure or a quoted value), so recovery aborts rather than guessing. The position reported is where the bareword started.

Source

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

	 *   unquoted keys, so a missed comma (`{"a": foo "b": 1}`, `{a: foo b: 1}`)
	 *   would otherwise silently swallow the following field. A colon followed
	 *   by `/` or `\` stays literal so URL and Windows-path values recover;
	 * - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
	 */
	#bareword(): string {
		const s = this.#s;
		const start = this.#i;
		let i = start;
		while (i < this.#n) {
			const cc = s.charCodeAt(i);
			if (cc === 0x2c /* , */ || cc === 0x7d /* } */ || cc === 0x5d /* ] */ || cc === 0x0a || cc === 0x0d) break;
			if (
				cc === QUOTE ||
				cc === 0x7b /* { */ ||
				cc === 0x5b /* [ */ ||
				(cc === 0x3a /* : */ && s.charCodeAt(i + 1) !== 0x2f /* / */ && s.charCodeAt(i + 1) !== 0x5c) /* \ */
			) {
				throw new SyntaxError(`Unexpected token at position ${start}`);
			}
			i++;
		}
		if (i >= this.#n) throw new SyntaxError(`Unexpected token at position ${start}`);
		let end = i;
		while (end > start && isWhitespace(s.charCodeAt(end - 1))) end--;
		const word = s.slice(start, end);
		if (NON_RECOVERABLE_BAREWORDS[word]) throw new SyntaxError(`Unexpected token at position ${start}`);
		this.#i = i;
		return word;
	}
}

/**
 * Final-parse a JSON value, repairing the common LLM malformations
 * ({@link RelaxedJson}). Tries strict `JSON.parse` first (fast path, exact JSON
 * semantics), then the relaxed parser. Throws when the input is unrepairable,
 * truncated, or carries trailing garbage — so callers can skip a bad tool call

View on GitHub (pinned to 9690622007)

Solutions

  1. Quote the offending value: {"a": "[1,2]"} instead of {"a": [1,2]} where you meant a string.
  2. Check the character at the reported position — quotes/brackets/colons cannot appear in an unquoted bareword.
  3. If input is truncated, supply the rest of the input; the parser cannot recover a bareword that never terminates.
  4. Use proper JSON (run it through a JSON formatter) instead of relying on the recovery path.

Example fix

// before
parse('{"pattern": "*.ts"}'); // missing colon handling in bareword → SyntaxError at start
parse('{"x": [1,2]}'); // intended as string
// after
parse('{"x": "[1,2]"}'); // quote values containing JSON structural characters
Defensive patterns

Strategy: validation

Validate before calling

// reject unquoted values containing structural characters before recovery parse
const badUnquoted = /(^|[\[,:]\s*)(?!["\d{\[tfn\-])([^\s,"}\]]*["\[{:]|.*:\S)/;
if (badUnquoted.test(input)) {
  throw new SyntaxError("Unquoted value contains structural characters; quote it");
}

Try / catch

try {
  return parse(input);
} catch (err) {
  if (err instanceof SyntaxError && err.message.startsWith("Unexpected token at position")) {
    const start = Number(err.message.match(/position (\d+)/)?.[1]);
    throw new SyntaxError(
      `Unquoted string at ${start} contains quote/bracket/colon — quote the value`,
    );
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parse with an unquoted value that contains structural characters, e.g. '{"a": [1,2]}' as a bareword value ( '[' inside the word), '{"a": "x"}' missing the opening quote handling (a quote mid-scan), or '{"a": b:c}' (':' without a following '/' or '\\'). Also thrown at the second site when the scan runs past end-of-input before finding a terminator.

Common situations: Recovery attempts on partially-corrupt JSON produced by LLM output, template substitutions injecting unquoted values containing JSON syntax, unquoted globs/paths like packages/* which are fine but windows paths C:\\... vs URLs http:// which interact with the ':' rule, and truncated files (scan hits EOF).

Understand the failure class

Related errors


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