{"record":{"id":"988077f2fb7c92ce","repo":"windmill-labs/windmill","slug":"invalid-note-at-index-index-position-must-be-a","errorCode":null,"errorMessage":"Invalid note at index ${index}: position must be an object with numeric x and y","messagePattern":"Invalid note at index (.+?): position must be an object with numeric x and y","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/helperUtils.ts","lineNumber":201,"sourceCode":"\t\tif (type !== 'free' && type !== 'group') {\n\t\t\tthrow new Error(`Invalid note at index ${index}: type must be \"free\" or \"group\"`)\n\t\t}\n\t\tif (n.color !== undefined && n.color !== null) {\n\t\t\tif (typeof n.color !== 'string' || !ALLOWED_NOTE_COLORS.has(n.color)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid note at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (n.position !== undefined && n.position !== null) {\n\t\t\tconst p = n.position as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof p !== 'object' ||\n\t\t\t\tArray.isArray(n.position) ||\n\t\t\t\ttypeof p.x !== 'number' ||\n\t\t\t\ttypeof p.y !== 'number'\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid note at index ${index}: position must be an object with numeric x and y`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (n.size !== undefined && n.size !== null) {\n\t\t\tconst s = n.size as Record<string, unknown>\n\t\t\tif (\n\t\t\t\ttypeof s !== 'object' ||\n\t\t\t\tArray.isArray(n.size) ||\n\t\t\t\ttypeof s.width !== 'number' ||\n\t\t\t\ttypeof s.height !== 'number'\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid note at index ${index}: size must be an object with numeric width and height`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (type === 'group' && n.contained_node_ids !== undefined) {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts#L183-L219","documentation":"validateFlowNotes validates each note in the AI copilot's flow-tools `notes` argument before the notes are attached to the flow. A note may optionally carry a `position` for explicit placement; when present it must be a plain object with numeric `x` and `y` fields. The library throws this error when the position fails that shape check, so malformed geometry from the LLM never reaches the flow editor.","triggerScenarios":"Calling the flow-tools note/flowTools entry point with notes[i].position set to a string like \"{x:0,y:0}\", an array like [0,0], or an object with string values ({x: \"10\", y: \"20\"}) or missing one coordinate.","commonSituations":"An LLM emits position coordinates as strings in the JSON tool call; the caller hand-builds notes and passes a tuple [x, y] instead of {x, y}; JSON containing coordinates gets re-parsed with number-coercion lost.","solutions":["Convert coordinates to numbers and pass a plain object: position: { x: Number(x), y: Number(y) }.","If the position arrives as an array [x, y], spread it into an object: { x: arr[0], y: arr[1] }.","Omit `position` entirely (or set it to null) to let the validator auto-place the note.","Check the index in the error message and log the offending note to confirm which field is malformed."],"exampleFix":"// before\nnotes: [{ id: 'n1', text: 'review', position: [10, 20] }]\n// after\nnotes: [{ id: 'n1', text: 'review', position: { x: 10, y: 20 } }]","handlingStrategy":"validation","validationCode":"function hasValidPosition(n) {\n  return n.position == null ||\n    (typeof n.position === 'object' && !Array.isArray(n.position) &&\n     typeof n.position.x === 'number' && typeof n.position.y === 'number')\n}\nnotes.forEach((n, i) => { if (!hasValidPosition(n)) throw new Error(`note ${i}: bad position`) })","typeGuard":"function isPosition(p): p is { x: number; y: number } {\n  return typeof p === 'object' && p !== null && !Array.isArray(p) &&\n    typeof (p as any).x === 'number' && typeof (p as any).y === 'number'\n}","tryCatchPattern":"try {\n  validateFlowNotes(notes)\n} catch (e) {\n  if (e.message.includes('position')) console.warn('dropping note position, will auto-place')\n}","preventionTips":["Always build position as { x: number, y: number }, never arrays or strings","Coerce LLM-emitted coordinates with Number() before passing","Omit position when auto-placement is acceptable"],"tags":["validation","typescript","flow-notes","schema"],"backgroundTag":"invalid-tool-argument-shape","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"}