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 callView on GitHub (pinned to 9690622007)
Solutions
- Quote the offending value: {"a": "[1,2]"} instead of {"a": [1,2]} where you meant a string.
- Check the character at the reported position — quotes/brackets/colons cannot appear in an unquoted bareword.
- If input is truncated, supply the rest of the input; the parser cannot recover a bareword that never terminates.
- 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
- Quote any value containing ", {, [, or : — bareword recovery only handles plain strings/globs/paths.
- Remember barewords cannot contain ':' unless followed by / or \\ (URLs like http:// are OK; 'a:b' is not).
- Prefer emitting fully valid JSON from producers instead of relying on recovery.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unexpected end of JSON input
- Unexpected token at position ${this.#i}
- Replacement text is not valid UTF-8: {err}
- V2 compaction stream parse failed: ${err instanceof Error ?
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE must contain a JSON object
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/894cf38253e35a0a.
Report an issue: GitHub.