{"record":{"id":"22d1189e601be1e8","repo":"windmill-labs/windmill","slug":"flow-notes-must-be-an-array","errorCode":null,"errorMessage":"Flow notes must be an array","messagePattern":"Flow notes must be an array","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/helperUtils.ts","lineNumber":156,"sourceCode":" * `set_flow_json` rather than being rejected. When `moduleIds` is provided,\n * every `contained_node_ids` entry of a `group` note must reference an existing\n * module.\n *\n * A provided palette `color` is always preserved as-is; the default is only\n * filled in when a note omits `color` entirely (FlowNote.color is required).\n *\n * Free notes are also given a concrete `position` and `size` when missing. A\n * free note without geometry is not draggable/resizable in the editor (you'd\n * have to resize it first to give it a size) — UI-created notes always set both,\n * so agent-created notes must too. Provided geometry is preserved untouched.\n */\nexport function validateFlowNotes(rawNotes: unknown, moduleIds?: Set<string>): FlowNote[] | null {\n\tif (rawNotes == null) {\n\t\treturn null\n\t}\n\n\tif (!Array.isArray(rawNotes)) {\n\t\tthrow new Error('Flow notes must be an array')\n\t}\n\n\tconst seenIds = new Set<string>()\n\t// Column and running y-cursor for auto-placed free notes so consecutive ones\n\t// stack below each other by their actual heights instead of overlapping. A\n\t// preserved note (explicit geometry) sitting in this column also advances the\n\t// cursor, so a later auto-placed note doesn't land on top of it.\n\tconst AUTO_STACK_X = -(MIN_NOTE_WIDTH + 100)\n\tconst AUTO_STACK_GAP = 24\n\tlet autoStackY = 0\n\treturn rawNotes.map((note, index) => {\n\t\tif (!note || typeof note !== 'object' || Array.isArray(note)) {\n\t\t\tthrow new Error(`Invalid note at index ${index}: must be an object`)\n\t\t}\n\t\tconst n = note as Record<string, unknown>\n\t\tif (typeof n.id !== 'string' || !n.id) {\n\t\t\tthrow new Error(`Invalid note at index ${index}: id must be a non-empty string`)\n\t\t}","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts#L138-L174","documentation":"validateFlowNotes validates the optional `notes` array attached to a flow update. If rawNotes is neither null/undefined nor an array (e.g. an object or string was passed), this error is thrown. It enforces the FlowNote[] shape the note editor expects before notes are placed on the canvas.","triggerScenarios":"Calling patch_flow_json/set_flow_json with notes: { id: 'n1', text: 'hi' } (single object instead of array) or notes: 'some text'.","commonSituations":"An LLM agent emits a single note object instead of wrapping it in an array; a caller confuses notes with a map keyed by id; JSON was flattened by an intermediate serialization step.","solutions":["Wrap the note object(s) in an array: notes: [{ id: 'n1', text: '...' }]","Pass null or omit notes entirely if there are no notes to set","Ensure the argument is a JSON array before calling the tool"],"exampleFix":"// before\nnotes: { id: 'n1', text: 'Review this' }\n// after\nnotes: [{ id: 'n1', text: 'Review this' }]","handlingStrategy":"type-guard","validationCode":"if (notes != null && !Array.isArray(notes)) {\n  notes = [notes]; // or throw before calling the tool\n}","typeGuard":"function isFlowNoteArray(v: unknown): v is FlowNote[] {\n  return v == null || (Array.isArray(v) && v.every((n) => n !== null && typeof n === 'object' && !Array.isArray(n)));\n}","tryCatchPattern":"try {\n  const notes = validateFlowNotes(rawNotes, moduleIds);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Flow notes must be an array') {\n    // coerce to [rawNotes] and retry once\n  } else throw e;\n}","preventionTips":["Always pass notes as a JSON array, even for a single note","Use null/undefined (or omit the field) when there are no notes","Validate payload shape with a schema (zod) before calling the tool"],"tags":["validation","flow-notes","type-mismatch","array"],"backgroundTag":"invalid-argument-value","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"}