windmill-labs/windmill · error · Error

Invalid note at index ${index}: id must be a non-empty strin

Error message

Invalid note at index ${index}: id must be a non-empty string

What it means

Thrown by validateFlowNotes when the flow note at `index` lacks a non-empty string `id`. Notes are keyed into the flow draft by id and auto-placement needs a stable identity, so LLM-supplied note arrays without ids are rejected by this guard before the draft is written.

Source

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

	if (!Array.isArray(rawNotes)) {
		throw new Error('Flow notes must be an array')
	}

	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(', ')}`
				)
			}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Add a unique non-empty string id to every note, e.g. id: 'note-1'
  2. Convert numeric ids to strings before passing
  3. Generate ids yourself (e.g. crypto.randomUUID()) when creating notes programmatically

Example fix

// before
notes: [{ text: 'Review auth flow' }]
// after
notes: [{ id: 'note-1', text: 'Review auth flow' }]
Defensive patterns

Strategy: validation

Validate before calling

notes.forEach((n, i) => {
  if (typeof n.id !== 'string' || !n.id) throw new Error(`notes[${i}].id must be a non-empty string`);
});

Type guard

function hasNoteId(n: unknown): n is { id: string } & Record<string, unknown> {
  return typeof (n as any)?.id === 'string' && (n as any).id.length > 0;
}

Try / catch

try {
  validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
  if (e instanceof Error && e.message.includes('id must be a non-empty string')) {
    // regenerate ids and retry
  } else throw e;
}

Prevention

When it happens

Trigger: notes: [{ text: 'hi' }], notes: [{ id: '', text: 'hi' }], or notes: [{ id: 42, text: 'hi' }] passed to patch_flow_json/set_flow_json.

Common situations: An LLM agent omits ids for brevity; a caller assumes ids are auto-generated (they are not, unlike UI-created notes); numeric database keys were passed instead of strings.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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