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
- Assign a distinct id to each note in the array
- If two notes should be identical, still give them different ids (e.g. n1, n2)
- 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
- Generate ids from a counter or UUID so collisions cannot happen
- Deduplicate merged note lists before submitting
- When duplicating a note as a template, always change its id
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
- Invalid note at index ${index}: id must be a non-empty strin
- Flow notes must be an array
- Invalid note at index ${index}: must be an object
- Invalid note at index ${index}: text must be a string
- Invalid note at index ${index}: type must be "free" or "grou
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/e0b53fe84cf1c87b.
Report an issue: GitHub.