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
- Use for...of for strings/arrays/Maps/Sets: `for (const ch of str)`.
- Use `Object.keys(obj)` / `Object.entries(obj)` to get an explicit key list, then for...of.
- 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
- Prefer Object.keys/entries + for...of over for...in.
- Never for...in over strings, Maps, Sets, numbers, or null.
- Follow the error hint: for...of for iterables, Object.keys for key lists.
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
- for...in supports one declared binding.
- Unsupported for...in binding.
- Expected '${key}' to be a boolean.
- Failed to parse script as a Program node.
- String.${name} expects argument ${index + 1} to be a number.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/a66ead55df7d3d8f.
Report an issue: GitHub.