{"record":{"id":"c51b456b9f07a522","repo":"windmill-labs/windmill","slug":"value-modules-must-be-a-list","errorCode":null,"errorMessage":"'value.modules' must be a list","messagePattern":"'value\\.modules' must be a list","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/flows/header/FlowYamlEditor.svelte","lineNumber":47,"sourceCode":"\t\tcode = YAML.stringify(filteredContentForExport(flowStore.val))\n\t\tinitialCode = code\n\t\teditor?.setCode(code)\n\t}\n\n\t/** `value` is assigned unconditionally below and `FlowEditor` dereferences\n\t * `flowStore.val.value.modules`, so an OpenFlow document missing it takes the\n\t * whole editor down mid-render — after the toast has already claimed success.\n\t * Reject it up front instead. */\n\tfunction validateShape(parsed: unknown) {\n\t\tif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n\t\t\tthrow new Error('the document must be a mapping (key: value)')\n\t\t}\n\t\tconst value = (parsed as Record<string, unknown>).value\n\t\tif (typeof value !== 'object' || value === null || Array.isArray(value)) {\n\t\t\tthrow new Error(\"missing 'value' - paste a whole OpenFlow document, not just its value\")\n\t\t}\n\t\tif (!Array.isArray((value as Record<string, unknown>).modules)) {\n\t\t\tthrow new Error(\"'value.modules' must be a list\")\n\t\t}\n\t}\n\n\tfunction validateGroups(groups: { start_id: string; end_id: string }[] | undefined) {\n\t\tif (!groups) return\n\t\tconst seen = new Set<string>()\n\t\tfor (const g of groups) {\n\t\t\tconst key = `${g.start_id}:${g.end_id}`\n\t\t\tif (seen.has(key)) {\n\t\t\t\tthrow new Error(`Duplicate group: '${g.start_id}' → '${g.end_id}'`)\n\t\t\t}\n\t\t\tseen.add(key)\n\t\t}\n\t}\n\n\tfunction apply() {\n\t\ttry {\n\t\t\tconst parsed = YAML.parse(code)","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/flows/header/FlowYamlEditor.svelte#L29-L65","documentation":"validateShape in FlowYamlEditor.svelte checks that the parsed OpenFlow document's 'value.modules' key is an array, since a Windmill flow body is a list of modules. It throws when 'value.modules' is absent, a scalar, or a non-array object — meaning the flow body shape is wrong even though the document has a proper 'value' mapping.","triggerScenarios":"Calling apply() → validateShape() with YAML where value.modules is missing, set to a single module object instead of a list, misspelled (e.g. 'value.Modules' or 'value.steps'), or a mapping of module-id → module.","commonSituations":"Editing YAML by hand and dropping the list syntax (a dash) so modules becomes a single object; porting from another flow format that uses a different key name for steps; accidentally deleting the modules list during a merge.","solutions":["Ensure 'value.modules' exists and is a YAML list, with each module prefixed by '- id: ...'","If you intended a single step, wrap it in a one-element list","Check the key spelling is exactly 'modules' and it is nested directly under 'value'","Validate against an exported flow's YAML to confirm the expected structure"],"exampleFix":"// before (modules as single object)\nvalue:\n  modules:\n    id: a\n    value:\n      type: rawscript\n// after (modules as a list)\nvalue:\n  modules:\n    - id: a\n      value:\n        type: rawscript","handlingStrategy":"validation","validationCode":"const parsed = yaml.load(text)\nif (!Array.isArray(parsed?.value?.modules)) {\n  alert('value.modules must be a YAML list of module objects')\n}","typeGuard":"function hasModulesList(v: unknown): v is { value: { modules: unknown[] } } {\n  return typeof v === 'object' && v !== null && Array.isArray((v as any).value?.modules)\n}","tryCatchPattern":"try {\n  editor.apply(yamlText)\n} catch (e) {\n  if (e.message.includes(\"'value.modules' must be a list\")) {\n    sendUserToast('Each module must be a list item: prefix with \"- \"', true)\n  }\n}","preventionTips":["Prefix every module with '-' so it parses as a list item","Use the exact key 'modules' nested under 'value'","Compare with an exported flow's YAML when in doubt","Round-trip edit: apply → re-download → edit → re-apply"],"tags":["yaml","validation","user-input"],"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"}