{"record":{"id":"e0b53fe84cf1c87b","repo":"windmill-labs/windmill","slug":"invalid-note-at-index-index-duplicate-note-id","errorCode":null,"errorMessage":"Invalid note at index ${index}: duplicate note id \"${n.id}\"","messagePattern":"Invalid note at index (.+?): duplicate note id \"(.+?)\"","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/helperUtils.ts","lineNumber":176,"sourceCode":"\n\tconst seenIds = new Set<string>()\n\t// Column and running y-cursor for auto-placed free notes so consecutive ones\n\t// stack below each other by their actual heights instead of overlapping. A\n\t// preserved note (explicit geometry) sitting in this column also advances the\n\t// cursor, so a later auto-placed note doesn't land on top of it.\n\tconst AUTO_STACK_X = -(MIN_NOTE_WIDTH + 100)\n\tconst AUTO_STACK_GAP = 24\n\tlet autoStackY = 0\n\treturn rawNotes.map((note, index) => {\n\t\tif (!note || typeof note !== 'object' || Array.isArray(note)) {\n\t\t\tthrow new Error(`Invalid note at index ${index}: must be an object`)\n\t\t}\n\t\tconst n = note as Record<string, unknown>\n\t\tif (typeof n.id !== 'string' || !n.id) {\n\t\t\tthrow new Error(`Invalid note at index ${index}: id must be a non-empty string`)\n\t\t}\n\t\tif (seenIds.has(n.id)) {\n\t\t\tthrow new Error(`Invalid note at index ${index}: duplicate note id \"${n.id}\"`)\n\t\t}\n\t\tseenIds.add(n.id)\n\t\tif (typeof n.text !== 'string') {\n\t\t\tthrow new Error(`Invalid note at index ${index}: text must be a string`)\n\t\t}\n\t\tconst type = n.type ?? 'free'\n\t\tif (type !== 'free' && type !== 'group') {\n\t\t\tthrow new Error(`Invalid note at index ${index}: type must be \"free\" or \"group\"`)\n\t\t}\n\t\tif (n.color !== undefined && n.color !== null) {\n\t\t\tif (typeof n.color !== 'string' || !ALLOWED_NOTE_COLORS.has(n.color)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid note at index ${index}: color must be one of ${[...ALLOWED_NOTE_COLORS].join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (n.position !== undefined && n.position !== null) {\n\t\t\tconst p = n.position as Record<string, unknown>","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/helperUtils.ts#L158-L194","documentation":"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.","triggerScenarios":"notes: [{ id: 'n1', text: 'a' }, { id: 'n1', text: 'b' }] passed to patch_flow_json/set_flow_json.","commonSituations":"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.","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"],"exampleFix":"// before\nnotes: [{ id: 'n1', text: 'a' }, { id: 'n1', text: 'b' }]\n// after\nnotes: [{ id: 'n1', text: 'a' }, { id: 'n2', text: 'b' }]","handlingStrategy":"validation","validationCode":"const seen = new Set<string>();\nnotes.forEach((n, i) => {\n  if (seen.has(n.id)) throw new Error(`notes[${i}] duplicates id \"${n.id}\"`);\n  seen.add(n.id);\n});","typeGuard":"function hasUniqueNoteIds(notes: { id: string }[]): boolean {\n  return new Set(notes.map((n) => n.id)).size === notes.length;\n}","tryCatchPattern":"try {\n  validateFlowNotes(rawNotes, moduleIds);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('duplicate note id')) {\n    // re-index ids (n1, n2, ...) and retry\n  } else throw e;\n}","preventionTips":["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"],"tags":["validation","flow-notes","duplicate-key","identifier"],"backgroundTag":"duplicate-identifier","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}