windmill-labs/windmill · error · Error

Invalid note at index ${index}: position must be an object w

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.

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)

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.