different-ai/openwork · error · InterpreterRuntimeError

for...in requires a plain object, array, or tools reference

Error message

for...in requires a plain object, array, or tools reference in CodeMode. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list.

What it means

for...in in CodeMode enumerates keys only of plain objects, arrays, or tools references — the same enumeration Object.keys performs. Anything else (strings, Maps, Sets, numbers, null) is a deliberate error instead of real JS's surprising behavior (indices for strings, zero iterations for Maps/Sets/null). The message includes a hint pointing to for...of and Object.keys.

Source

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

    return undefined
  }

  private evaluateForInStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
    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")

      // Keys are snapshotted up front (mutation during iteration is safe): plain objects
      // enumerate their own keys, arrays their index strings, and tool references the
      // namespace/tool names at that node - the same enumeration Object.keys performs.
      // Anything else (strings, Maps, Sets, numbers, null, ...) is a deliberate error rather
      // than real JS's surprising behavior (indices for strings, zero iterations for
      // Maps/Sets/null): the hint points at the constructs that do what the program means.
      const keys = self.enumerableKeys(right)
      if (keys === undefined) {
        throw new InterpreterRuntimeError(
          "for...in requires a plain object, array, or tools reference in CodeMode. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list.",
          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...in 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")

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Use for...of for strings/arrays/Maps/Sets: `for (const ch of str)`.
  2. Use `Object.keys(obj)` / `Object.entries(obj)` to get an explicit key list, then for...of.
  3. Check the value's runtime type; if it is a Map, iterate `map.entries()` with for...of.

Example fix

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

Strategy: type-guard

Validate before calling

// only for...in over plain objects / arrays / tools refs
const okForIn = (v: unknown) =>
  v !== null && (typeof v === "object" || typeof v === "function") &&
  !(v instanceof Map) && !(v instanceof Set) && typeof v !== "string";

Type guard

const isForInTarget = (v: unknown): v is Record<string, unknown> | unknown[] =>
  typeof v === "object" && v !== null && !(v instanceof Map) && !(v instanceof Set);

Try / catch

try {
  interpret(src);
} catch (e) {
  if (e instanceof InterpreterRuntimeError && e.message.includes("for...in requires")) {
    // switch to for...of or Object.keys per the message hint
  }
  throw e;
}

Prevention

When it happens

Trigger: `for (const k in value)` where value is a string, Map, Set, number, null, or undefined; enumerableKeys returns undefined for those types.

Common situations: Porting JS that used for...in over a Map/Set (silently did nothing in JS); iterating string characters with for...in; using for...in on a tool namespace object is fine but on anything else fails.

Related errors


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