windmill-labs/windmill · error · Error

Invalid note at index ${index}: duplicate note id "${n.id}"

Error message

Invalid note at index ${index}: duplicate note id "${n.id}"

What it means

Note ids must be unique within the notes array because the editor uses them as keys for each canvas annotation. validateFlowNotes tracks seen ids and throws this error on the second occurrence of the same id.

Source

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

	const seenIds = new Set<string>()
	// Column and running y-cursor for auto-placed free notes so consecutive ones
	// stack below each other by their actual heights instead of overlapping. A
	// preserved note (explicit geometry) sitting in this column also advances the
	// cursor, so a later auto-placed note doesn't land on top of it.
	const AUTO_STACK_X = -(MIN_NOTE_WIDTH + 100)
	const AUTO_STACK_GAP = 24
	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>

View on GitHub (pinned to e474e8803c)

Solutions

  1. Assign a distinct id to each note in the array
  2. If two notes should be identical, still give them different ids (e.g. n1, n2)
  3. Deduplicate the input list before calling the tool if duplicates are unintended

Example fix

// before
notes: [{ id: 'n1', text: 'a' }, { id: 'n1', text: 'b' }]
// after
notes: [{ id: 'n1', text: 'a' }, { id: 'n2', text: 'b' }]
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set<string>();
notes.forEach((n, i) => {
  if (seen.has(n.id)) throw new Error(`notes[${i}] duplicates id "${n.id}"`);
  seen.add(n.id);
});

Type guard

function hasUniqueNoteIds(notes: { id: string }[]): boolean {
  return new Set(notes.map((n) => n.id)).size === notes.length;
}

Try / catch

try {
  validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
  if (e instanceof Error && e.message.includes('duplicate note id')) {
    // re-index ids (n1, n2, ...) and retry
  } else throw e;
}

Prevention

When it happens

Trigger: notes: [{ id: 'n1', text: 'a' }, { id: 'n1', text: 'b' }] passed to patch_flow_json/set_flow_json.

Common situations: An LLM agent copies a note entry to create a similar note and forgets to change the id; a merge of note lists from two sources reuses ids; a loop counter wasn't incremented when generating ids.

Related errors


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