different-ai/openwork · error · InterpreterRuntimeError

Unsupported for...of binding.

Error message

Unsupported for...of binding.

What it means

The for...of left side must be either a single VariableDeclaration or a plain Identifier (assignment to an existing variable). Any other left-side node kind (member expression like `obj.x`, complex patterns beyond the supported ones, etc.) throws this error at the left node. The interpreter implements only these two binding forms.

Source

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

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

      for (const value of iterable) {
        if (declaration) {
          self.pushScope()
          yield* self.declarePattern(declaration.pattern, value, declaration.mutable, left)
        } else if (assignmentName) {
          self.setIdentifierValue(assignmentName, value, left)
        }

        const result = yield* self.evaluateStatement(body).pipe(
          Effect.ensuring(
            Effect.sync(() => {
              if (declaration) self.popScope()
            }),
          ),
        )

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Declare a loop variable, then assign to the member inside the body: `for (const v of items) { obj.key = v; }`.
  2. Use an existing plain variable as the loop target instead of a property path.
  3. If a pattern is needed, keep it to destructuring supported by declarePattern inside a single VariableDeclaration.

Example fix

// before
for (obj.key of items) { ... }
// after
for (const v of items) { obj.key = v; }
Defensive patterns

Strategy: validation

Validate before calling

// AST check before execution
const t = forOfNode.left.type;
if (t !== "VariableDeclaration" && t !== "Identifier") {
  throw new Error("for...of target must be a declaration or identifier");
}

Type guard

const isValidForOfLeft = (n: AstNode): boolean =>
  n.type === "Identifier" ||
  (n.type === "VariableDeclaration" && Array.isArray(n.declarations) && n.declarations.length === 1);

Try / catch

try {
  interpret(src);
} catch (e) {
  if (e instanceof InterpreterRuntimeError && e.message.includes("Unsupported for...of binding")) {
    // rewrite to declare + in-body assignment
  }
  throw e;
}

Prevention

When it happens

Trigger: `for (obj.key of items)`, `for (arr[0] of items)`, or any AST left node that is neither VariableDeclaration nor Identifier.

Common situations: Porting JS that assigns to an object property per iteration; LLM-generated ASTs using unsupported node kinds for the loop target.

Related errors


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