windmill-labs/windmill · error · Error

Invalid note at index

Error message

Invalid note at index ${index}: position must be an object with numeric x and y

What it means

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.

Solutions

  1. Convert coordinates to numbers and pass a plain object: position: { x: Number(x), y: Number(y) }.
  2. If the position arrives as an array [x, y], spread it into an object: { x: arr[0], y: arr[1] }.
  3. Omit `position` entirely (or set it to null) to let the validator auto-place the note.
  4. Check the index in the error message and log the offending note to confirm which field is malformed.

Example fix

// before
notes: [{ id: 'n1', text: 'review', position: [10, 20] }]
// after
notes: [{ id: 'n1', text: 'review', position: { x: 10, y: 20 } }]
Defensive patterns

Strategy: validation

Validate before calling

function hasValidPosition(n) {
  return n.position == null ||
    (typeof n.position === 'object' && !Array.isArray(n.position) &&
     typeof n.position.x === 'number' && typeof n.position.y === 'number')
}
notes.forEach((n, i) => { if (!hasValidPosition(n)) throw new Error(`note ${i}: bad position`) })

Type guard

function isPosition(p): p is { x: number; y: number } {
  return typeof p === 'object' && p !== null && !Array.isArray(p) &&
    typeof (p as any).x === 'number' && typeof (p as any).y === 'number'
}

Try / catch

try {
  validateFlowNotes(notes)
} catch (e) {
  if (e.message.includes('position')) console.warn('dropping note position, will auto-place')
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/988077f2fb7c92ce. Report an issue: GitHub.

Appendix: source

Thrown at frontend/src/lib/components/copilot/chat/flow/helperUtils.ts:201

		if (type !== 'free' && type !== 'group') {
			throw new Error(`Invalid note at index ${index}: type must be "free" or "group"`)
		}
		if (n.color !== undefined && n.color !== null) {
			if (typeof n.color !== 'string' || !ALLOWED_NOTE_COLORS.has(n.color)) {
				throw new Error(
					`Invalid note at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`
				)
			}
		}
		if (n.position !== undefined && n.position !== null) {
			const p = n.position as Record<string, unknown>
			if (
				typeof p !== 'object' ||
				Array.isArray(n.position) ||
				typeof p.x !== 'number' ||
				typeof p.y !== 'number'
			) {
				throw new Error(
					`Invalid note at index ${index}: position must be an object with numeric x and y`
				)
			}
		}
		if (n.size !== undefined && n.size !== null) {
			const s = n.size as Record<string, unknown>
			if (
				typeof s !== 'object' ||
				Array.isArray(n.size) ||
				typeof s.width !== 'number' ||
				typeof s.height !== 'number'
			) {
				throw new Error(
					`Invalid note at index ${index}: size must be an object with numeric width and height`
				)
			}
		}
		if (type === 'group' && n.contained_node_ids !== undefined) {

View on GitHub (pinned to e474e8803c)