{"record":{"id":"a66ead55df7d3d8f","repo":"different-ai/openwork","slug":"for-in-requires-a-plain-object-array-or-tools","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"InterpreterRuntimeError","httpStatus":null,"severity":"error","filePath":"packages/codemode/src/interpreter/runtime.ts","lineNumber":1179,"sourceCode":"    return undefined\n  }\n\n  private evaluateForInStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {\n    const self = this\n    return Effect.gen(function* () {\n      const left = getNode(node, \"left\")\n      const right = yield* self.evaluateExpression(getNode(node, \"right\"))\n      const body = getNode(node, \"body\")\n\n      // Keys are snapshotted up front (mutation during iteration is safe): plain objects\n      // enumerate their own keys, arrays their index strings, and tool references the\n      // namespace/tool names at that node - the same enumeration Object.keys performs.\n      // Anything else (strings, Maps, Sets, numbers, null, ...) is a deliberate error rather\n      // than real JS's surprising behavior (indices for strings, zero iterations for\n      // Maps/Sets/null): the hint points at the constructs that do what the program means.\n      const keys = self.enumerableKeys(right)\n      if (keys === undefined) {\n        throw new InterpreterRuntimeError(\n          \"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.\",\n          node,\n        )\n      }\n\n      let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined\n      let assignmentName: string | undefined\n\n      if (left.type === \"VariableDeclaration\") {\n        const declarations = getArray(left, \"declarations\")\n        if (declarations.length !== 1) {\n          throw new InterpreterRuntimeError(\"for...in supports one declared binding.\", left)\n        }\n\n        const declarator = asNode(declarations[0], \"declarations[0]\")\n        declaration = { pattern: getNode(declarator, \"id\"), mutable: getString(left, \"kind\") !== \"const\" }\n      } else if (left.type === \"Identifier\") {\n        assignmentName = getString(left, \"name\")","sourceCodeStart":1161,"sourceCodeEnd":1197,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/codemode/src/interpreter/runtime.ts#L1161-L1197","documentation":"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.","triggerScenarios":"`for (const k in value)` where value is a string, Map, Set, number, null, or undefined; enumerableKeys returns undefined for those types.","commonSituations":"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.","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."],"exampleFix":"// before\nfor (const k in map) { ... }\n// after\nfor (const [k, v] of map.entries()) { ... }","handlingStrategy":"type-guard","validationCode":"// only for...in over plain objects / arrays / tools refs\nconst okForIn = (v: unknown) =>\n  v !== null && (typeof v === \"object\" || typeof v === \"function\") &&\n  !(v instanceof Map) && !(v instanceof Set) && typeof v !== \"string\";","typeGuard":"const isForInTarget = (v: unknown): v is Record<string, unknown> | unknown[] =>\n  typeof v === \"object\" && v !== null && !(v instanceof Map) && !(v instanceof Set);","tryCatchPattern":"try {\n  interpret(src);\n} catch (e) {\n  if (e instanceof InterpreterRuntimeError && e.message.includes(\"for...in requires\")) {\n    // switch to for...of or Object.keys per the message hint\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["codemode","interpreter","for-in","enumeration"],"backgroundTag":"invalid-for-in-target","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}