different-ai/openwork · error · InterpreterRuntimeError

Switch case values must be data values in CodeMode.

Error message

Switch case values must be data values in CodeMode.

What it means

Every `case` test value in a CodeMode switch must also be a pure data value. After evaluating a case expression, the interpreter checks it with containsOpaqueReference and throws InvalidDataValue at the case node if the candidate value carries an opaque reference. This keeps case matching strictly value-based (=== on data), never reference-based.

Source

Thrown at packages/codemode/src/interpreter/runtime.ts:916

      if (containsOpaqueReference(discriminant)) {
        throw new InterpreterRuntimeError(
          "Switch discriminants must be data values in CodeMode.",
          node,
          "InvalidDataValue",
        )
      }
      const cases = getArray(node, "cases").map((value, index) => asNode(value, `cases[${index}]`))
      let defaultIndex: number | undefined
      let selected: number | undefined
      for (const [index, branch] of cases.entries()) {
        const test = getOptionalNode(branch, "test")
        if (!test) {
          defaultIndex = index
          continue
        }
        const candidate = yield* self.evaluateExpression(test)
        if (containsOpaqueReference(candidate)) {
          throw new InterpreterRuntimeError(
            "Switch case values must be data values in CodeMode.",
            test,
            "InvalidDataValue",
          )
        }
        if (candidate === discriminant) {
          selected = index
          break
        }
      }
      const start = selected ?? defaultIndex
      if (start === undefined) return { kind: "none" } satisfies StatementResult
      for (let index = start; index < cases.length; index += 1) {
        for (const statementValue of getArray(cases[index]!, "consequent")) {
          const result = yield* self.evaluateStatement(asNode(statementValue, "consequent"))
          if (result.kind === "break") return { kind: "none" } satisfies StatementResult
          if (result.kind === "return" || result.kind === "continue") return result
          if (result.kind === "value") self.lastValue = result.value

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Use data literals (strings/numbers) as case values and map them to actions afterward.
  2. Convert the reference to a comparable data value (e.g. `.name`, `.id`) in both the discriminant and the case.
  3. Replace the switch with if/else using explicit reference equality when identity checks are intended.

Example fix

// before
switch (name) {
  case fsReadTool: ... }
// after
switch (name) {
  case "fs.read": ... }
Defensive patterns

Strategy: validation

Validate before calling

// validate every case expression result is data before matching
for (const c of caseValues) {
  if (typeof c === "function" || (c && typeof c === "object" && c.__opaque)) {
    throw new Error("case values must be data values");
  }
}

Type guard

const isPrimitiveCase = (v: unknown): v is string | number | boolean | null =>
  v === null || ["string", "number", "boolean"].includes(typeof v);

Try / catch

try {
  interpret(src);
} catch (e) {
  if (e instanceof InterpreterRuntimeError && e.code === "InvalidDataValue") {
    // convert the offending case label to a literal
  }
  throw e;
}

Prevention

When it happens

Trigger: A `case someExpression:` where the expression evaluates to an opaque reference — e.g. `case someFn:` comparing against a function, or `case tools.fs.read:` comparing against a tool handle.

Common situations: LLM-generated CodeMode code that lists function or tool references as case labels, a pattern valid in real JavaScript but rejected by the sandbox.

Related errors


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