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.valueView on GitHub (pinned to 2b7df46e8a)
Solutions
- Use data literals (strings/numbers) as case values and map them to actions afterward.
- Convert the reference to a comparable data value (e.g. `.name`, `.id`) in both the discriminant and the case.
- 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
- Use string/number literals as case labels only.
- Map case labels to actions after the switch, not via references in labels.
- Keep tool/function references out of switch heads entirely.
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
- Switch discriminants must be data values in CodeMode.
- Expected '${key}' to be a boolean.
- Failed to parse script as a Program node.
- String.${name} expects argument ${index + 1} to be a number.
- String.normalize expects the form "NFC", "NFD", "NFKC", or "
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/adee2fe117793df2.
Report an issue: GitHub.