different-ai/openwork · error · InterpreterRuntimeError

for...of requires an array, string, Map, or Set value in Cod

Error message

for...of requires an array, string, Map, or Set value in CodeMode.

What it means

for...of in CodeMode only works over arrays, strings, Maps, and Sets (iterating over a snapshot). The right-hand expression is normalized via spreadItems; if that returns undefined the value is not one of these iterable data types and the interpreter throws. This deliberately excludes arbitrary iterables (generators, custom iterators, arguments) and non-iterables.

Source

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

    }).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
  }

  private evaluateForOfStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
    if (getBoolean(node, "await")) {
      throw new InterpreterRuntimeError("for await...of is not supported.", node)
    }

    const self = this
    return Effect.gen(function* () {
      const left = getNode(node, "left")
      const right = yield* self.evaluateExpression(getNode(node, "right"))
      const body = getNode(node, "body")

      // Arrays iterate in place; strings iterate code points; Maps iterate [key, value]
      // pairs and Sets iterate values over a snapshot (mutation during iteration is safe).
      const iterable = Array.isArray(right) ? right : spreadItems(right)
      if (iterable === undefined) {
        throw new InterpreterRuntimeError("for...of requires an array, string, Map, or Set value in CodeMode.", node)
      }

      let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined
      let assignmentName: string | undefined

      if (left.type === "VariableDeclaration") {
        const declarations = getArray(left, "declarations")
        if (declarations.length !== 1) {
          throw new InterpreterRuntimeError("for...of supports one declared binding.", left)
        }

        const declarator = asNode(declarations[0], "declarations[0]")
        declaration = { pattern: getNode(declarator, "id"), mutable: getString(left, "kind") !== "const" }
      } else if (left.type === "Identifier") {
        assignmentName = getString(left, "name")
      } else {
        throw new InterpreterRuntimeError("Unsupported for...of binding.", left)
      }

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Convert the value to an array first: `Object.entries(obj)`, `Object.keys(obj)`, or `Array.from(...)`.
  2. For Maps/Sets confirm the value actually is a Map or Set instance, not a plain object shaped like one.
  3. Materialize generator output into an array before looping (generators are unsupported).

Example fix

// before
for (const [k, v] of obj) { ... }
// after
for (const [k, v] of Object.entries(obj)) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure the loop target is CodeMode-iterable before running
const iterableKind = (v: unknown) =>
  Array.isArray(v) ? "array" : typeof v === "string" ? "string" :
  v instanceof Map ? "map" : v instanceof Set ? "set" : null;

Type guard

const isCodeModeIterable = (v: unknown): v is unknown[] | string | Map<unknown, unknown> | Set<unknown> =>
  Array.isArray(v) || typeof v === "string" || v instanceof Map || v instanceof Set;

Try / catch

try {
  interpret(src);
} catch (e) {
  if (e instanceof InterpreterRuntimeError && e.message.includes("for...of requires")) {
    // convert target via Object.entries/keys or Array.from
  }
  throw e;
}

Prevention

When it happens

Trigger: `for (const x of value)` where value is a number, null/undefined, a plain object, a generator, or any custom iterable — spreadItems yields undefined for these.

Common situations: Iterating object properties with for...of instead of for...in/Object.entries; assuming generator functions work in CodeMode; iterating a Map-like plain object.

Related errors


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