{"record":{"id":"68bbf0681ddddfd5","repo":"windmill-labs/windmill","slug":"flow-modules-must-be-an-array","errorCode":null,"errorMessage":"Flow modules must be an array","messagePattern":"Flow modules must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts","lineNumber":217,"sourceCode":"\t\t\t(Object.keys(jsonSchema).length === 1 && jsonSchema.$schema)\n\t\t) {\n\t\t\treturn null\n\t\t}\n\t\tconst formatted = formatJsonSchemaForError(jsonSchema)\n\t\tif (formatted && formatted !== 'unknown' && !formatted.startsWith('{')) return formatted\n\t\tif (formatted && formatted.startsWith('{') && formatted !== '{ }') return formatted\n\t} catch {\n\t\t// Ignore errors from toJSONSchema\n\t}\n\treturn null\n}\n\nexport function validateFlowModules(\n\trawModules: unknown,\n\tctx: FlowValidationContext = {}\n): FlowModule[] {\n\tif (!Array.isArray(rawModules)) {\n\t\tthrow new Error('Flow modules must be an array')\n\t}\n\n\tconst parsedModules = rawModules as FlowModule[]\n\tconst modulesSchema = ctx.modulesSchema ?? flowModulesSchema\n\tconst result = modulesSchema.safeParse(parsedModules)\n\tif (!result.success) {\n\t\tconst errors = result.error.issues.slice(0, 5).map((e) => {\n\t\t\tconst path = e.path\n\t\t\tconst moduleIndex = typeof path[0] === 'number' ? path[0] : undefined\n\t\t\tconst moduleId = moduleIndex !== undefined ? parsedModules[moduleIndex]?.id : undefined\n\t\t\tconst fieldPath = path.slice(1).join('.')\n\n\t\t\tlet message = e.message\n\t\t\tif (e.code === 'invalid_type') {\n\t\t\t\tconst targetSchema = getSchemaAtPath(\n\t\t\t\t\tmodulesSchema,\n\t\t\t\t\tpath as (string | number)[],\n\t\t\t\t\tparsedModules","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts#L199-L235","documentation":"validateFlowModules in editableFlowJson.ts is the shared validator for a flow's modules payload. Before schema-checking, it asserts the raw modules value is an array; anything else (object, string, null, undefined) throws this error. It exists to give callers an early, clear message instead of an opaque Zod type error.","triggerScenarios":"Calling validateFlowModules (directly or via flowTools 'modules' handling / validateEditableFlowJson) with a modules value that is not an array — e.g. an object keyed by module id, null, or a JSON string of the array.","commonSituations":"A model emits modules as a map ({\"id\": {...}}) instead of a list; the caller forgets to JSON.parse a string payload; a partially built object is passed where an array was expected.","solutions":["Pass modules as a JSON array of module objects: [{ id, value, ... }, ...].","If the payload is a JSON string, parse it with JSON.parse before validating.","If modules are keyed by id, convert the object to an array with Object.values()."],"exampleFix":"// before\nvalidateFlowJson({ modules: { a: { id: 'a' } } })\n// after\nvalidateFlowJson({ modules: [{ id: 'a', value: {...} }] })","handlingStrategy":"type-guard","validationCode":"if (!Array.isArray(payload.modules)) throw new Error('modules must be an array of module objects')","typeGuard":"function isModuleArray(v: unknown): v is FlowModule[] {\n  return Array.isArray(v) && v.every((m) => !!m && typeof m === 'object' && typeof (m as any).id === 'string')\n}","tryCatchPattern":"try {\n  validateFlowModules(rawModules)\n} catch (e) {\n  if (e.message === 'Flow modules must be an array') {\n    rawModules = Array.isArray(rawModules) ? rawModules : Object.values(rawModules ?? {})\n  } else throw e\n}","preventionTips":["Always serialize modules as a JSON array, never an id-keyed map","Parse string payloads with JSON.parse before validating","Shape payloads after the documented EditableFlowJson format"],"tags":["validation","zod","flow-modules","type-error"],"backgroundTag":"schema-validation-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}