windmill-labs/windmill · error · Error

Invalid note at index ${index}: type must be "free" or "grou

Error message

Invalid note at index ${index}: type must be "free" or "group"

What it means

The only accepted note types are 'free' (standalone canvas annotation, the default) and 'group' (deprecated for creation but still accepted for round-tripping existing flows). validateFlowNotes throws this error when note.type is any other value.

Source

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

	let autoStackY = 0
	return rawNotes.map((note, index) => {
		if (!note || typeof note !== 'object' || Array.isArray(note)) {
			throw new Error(`Invalid note at index ${index}: must be an object`)
		}
		const n = note as Record<string, unknown>
		if (typeof n.id !== 'string' || !n.id) {
			throw new Error(`Invalid note at index ${index}: id must be a non-empty string`)
		}
		if (seenIds.has(n.id)) {
			throw new Error(`Invalid note at index ${index}: duplicate note id "${n.id}"`)
		}
		seenIds.add(n.id)
		if (typeof n.text !== 'string') {
			throw new Error(`Invalid note at index ${index}: text must be a string`)
		}
		const type = n.type ?? 'free'
		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`

View on GitHub (pinned to e474e8803c)

Solutions

  1. Use type: 'free' (or omit type, which defaults to 'free') for standalone notes
  2. Use type: 'group' only for pre-existing group notes; prefer the separate groups array for new groupings
  3. Fix casing — the comparison is exact lowercase

Example fix

// before
notes: [{ id: 'n1', text: 'x', type: 'sticky' }]
// after
notes: [{ id: 'n1', text: 'x', type: 'free' }] // or omit type
Defensive patterns

Strategy: validation

Validate before calling

const TYPES = ['free', 'group'];
notes.forEach((n, i) => {
  if (n.type !== undefined && !TYPES.includes(n.type)) throw new Error(`notes[${i}].type "${n.type}" invalid`);
});

Type guard

function isValidNoteType(t: unknown): t is 'free' | 'group' {
  return t === 'free' || t === 'group';
}

Try / catch

try {
  validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
  if (e instanceof Error && e.message.includes('type must be')) {
    // drop the type field (defaults to 'free') and retry
  } else throw e;
}

Prevention

When it happens

Trigger: notes: [{ id: 'n1', text: 'x', type: 'sticky' }] or type: 'Free' (wrong case) or type: 'annotation' passed to patch_flow_json/set_flow_json.

Common situations: An LLM agent invents a type name; capitalization mismatch; an agent tries to create the deprecated 'group' notes under a new name instead of using the groups array.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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