different-ai/openwork · error · InterpreterRuntimeError
String.normalize expects the form "NFC", "NFD", "NFKC", or "
Error message
String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}). What it means
String.normalize only accepts Unicode normalization forms NFC, NFD, NFKC, or NFKD; the host engine throws a RangeError for anything else. The sandbox catches that RangeError and rethrows it as an InterpreterRuntimeError tagged .as("RangeError") with a message showing the JSON-encoded bad form. Passing undefined is allowed (defaults to NFC).
Source
Thrown at packages/codemode/src/interpreter/runtime.ts:382
case "trimStart":
case "trimLeft":
result = value.trimStart()
break
case "trimEnd":
case "trimRight":
result = value.trimEnd()
break
// Locale/options arguments are ignored: comparison runs with the host default locale, and
// the common use is a sort comparator where any consistent order works.
case "localeCompare":
result = value.localeCompare(str(0))
break
case "normalize": {
const form = optStr(0)
try {
result = value.normalize(form)
} catch {
throw new InterpreterRuntimeError(
`String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}).`,
node,
).as("RangeError")
}
break
}
case "split": {
if (args.length === 0) {
result = [value]
break
}
if (args[0] instanceof SandboxRegExp) {
result = value.split((args[0] as SandboxRegExp).regex, optNum(1))
break
}
const requestedLimit = optNum(1)
result = value.split(str(0), requestedLimit === undefined ? undefined : requestedLimit >>> 0)
breakView on GitHub (pinned to 2b7df46e8a)
Solutions
- Use exactly one of "NFC", "NFD", "NFKC", "NFKD" (case-sensitive, uppercase)
- Guard optional config values with form === undefined ? undefined : form.toUpperCase() and validate membership in the set
- Replace null with undefined (or omit the argument) so optStr treats it as the default NFC
- Verify the value is a string, not a number or object
Example fix
// before text.normalize(form) // form = "nfc" // after const FORMS = ["NFC", "NFD", "NFKC", "NFKD"] text.normalize(FORMS.includes(form) ? form : undefined)
Defensive patterns
Strategy: validation
Validate before calling
const FORMS = new Set(["NFC", "NFD", "NFKC", "NFKD"])
if (form !== undefined && !FORMS.has(form)) throw new TypeError(`normalize form must be one of ${[...FORMS]}, got ${JSON.stringify(form)}`)
text.normalize(form) Type guard
const isNormForm = (v) => v === undefined || ["NFC","NFD","NFKC","NFKD"].includes(v)
Try / catch
try { out = text.normalize(form) } catch (e) { if (String(e.message).includes("String.normalize")) out = text.normalize(); else throw e } Prevention
- Only pass the exact uppercase form strings
- Treat undefined as the default, not null
- Normalize/validate form values coming from config before use
- Avoid dynamic construction of the form name
When it happens
Trigger: Calling String('e').normalize('nfc') (lowercase), normalize('NFc'), normalize(''), normalize(null), or normalize of any string other than the exact four forms; note normalize(undefined) is fine via optStr but normalize(null) reaches the host as null and throws.
Common situations: Typo'd or lowercase form names; building the form dynamically from config/CLI flags; passing null from optional JSON fields expecting it to mean default; localizing code that used a locale-specific argument.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- String.repeat expects a finite non-negative count.
- String.${name} expects argument ${index + 1} to be a number.
- String.replaceAll requires a regular expression with the glo
- String.matchAll requires a regular expression with the globa
- Expected '${key}' to be a boolean.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/f742524457899311.
Report an issue: GitHub.