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)
      break

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Use exactly one of "NFC", "NFD", "NFKC", "NFKD" (case-sensitive, uppercase)
  2. Guard optional config values with form === undefined ? undefined : form.toUpperCase() and validate membership in the set
  3. Replace null with undefined (or omit the argument) so optStr treats it as the default NFC
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/f742524457899311. Report an issue: GitHub.