toon-format/toon · error · SyntaxError
Invalid escape sequence: \u${hex} is a lone surrogate. Suppl
Error message
Invalid escape sequence: \u${hex} is a lone surrogate. Supplementary code points MUST appear as literal UTF-8 What it means
TOON deliberately forbids \u escapes in the surrogate range D800–DFFF (lone surrogates). Supplementary code points must be written as literal UTF-8 characters. This avoids producing invalid strings and normalizes representation.
Source
Thrown at packages/toon/src/shared/string-utils.ts:94
i += 2
continue
}
if (next === DOUBLE_QUOTE) {
unescaped += DOUBLE_QUOTE
i += 2
continue
}
if (next === 'u') {
if (i + 6 > value.length) {
throw new SyntaxError(`Invalid escape sequence: truncated \\u escape at "${value.slice(i, i + 6)}"`)
}
const hex = value.slice(i + 2, i + 6)
if (!/^[0-9a-f]{4}$/i.test(hex)) {
throw new SyntaxError(`Invalid escape sequence: \\u must be followed by 4 hex digits, got "${hex}"`)
}
const codeUnit = Number.parseInt(hex, 16)
if (codeUnit >= 0xD800 && codeUnit <= 0xDFFF) {
throw new SyntaxError(`Invalid escape sequence: \\u${hex} is a lone surrogate. Supplementary code points MUST appear as literal UTF-8`)
}
unescaped += String.fromCodePoint(codeUnit)
i += 6
continue
}
throw new SyntaxError(`Invalid escape sequence: \\${next}`)
}
unescaped += value[i]
i++
}
return unescaped
}
/** Finds the index of the closing double quote, accounting for escape sequences. */
export function findClosingQuote(content: string, start: number): number {View on GitHub (pinned to 604eac266e)
Solutions
- Replace the surrogate-pair escape with the literal UTF-8 emoji/character (e.g. 😀)
- Re-encode the source using a converter that emits UTF-8 instead of surrogate escapes
- Combine surrogate pairs correctly before conversion if migrating from JSON
Example fix
// before (TOON input) emoji: "\uD83D\uDE00" // after emoji: "😀"
Defensive patterns
Strategy: validation
Validate before calling
function hasLoneSurrogateEscapes(s: string): boolean {
const hexes = [...s.matchAll(/\\u([dD][89abAB][0-9a-fA-F]{2}|[dD][cdefCDEF][0-9a-fA-F]{2})/g)].map(m => parseInt(m[1], 16))
return hexes.some(h => h >= 0xD800 && h <= 0xDFFF)
} Try / catch
try {
const value = decode(input)
} catch (e) {
if (e instanceof SyntaxError && e.message.includes('lone surrogate')) {
// convert surrogate pairs to real characters before decoding
}
throw e
} Prevention
- Convert JSON surrogate-pair escapes to literal UTF-8 during migration
- Ensure your toolchain treats all text as UTF-8 end to end
- Reject lone surrogates in your own input pipeline before they reach TOON
When it happens
Trigger: Parsing a TOON string containing \uD800–\uDFFF (e.g. "\uD83D" without its low surrogate, or any lone surrogate code).
Common situations: Converting JSON that used surrogate pairs (\uD83D\uDE00) into TOON; tools that escape astral characters as surrogate pairs rather than emitting UTF-8.
Related errors
- Invalid escape sequence: truncated \u escape at "${value.sli
- Invalid escape sequence: \u must be followed by 4 hex digits
- Cannot encode ${context} containing an unpaired surrogate U+
- Invalid escape sequence: backslash at end of string
- Invalid escape sequence: \${next}
AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31).
Data as JSON: /api/errors/18bc96c27689378a.
Report an issue: GitHub.