windmill-labs/windmill · error · Error

Invalid note at index

Error message

Invalid note at index ${index}: text must be a string

What it means

A note's text field holds the sticky-note content and must be a string (empty string allowed). validateFlowNotes throws this error when text is missing or of another type, since the renderer and editor operate on plain string content.

Solutions

  1. Provide text as a plain string on every note, even if empty ('')
  2. Stringify structured content (join lines with \n, serialize objects) before passing
  3. Convert numbers/booleans with String(...) if the content is scalar

Example fix

// before
notes: [{ id: 'n1', text: ['line1', 'line2'] }]
// after
notes: [{ id: 'n1', text: 'line1\nline2' }]
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try {
  validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
  if (e instanceof Error && e.message.includes('text must be a string')) {
    // stringify offending text fields and retry
  } else throw e;
}

Prevention

When it happens

Trigger: notes: [{ id: 'n1' }] (text missing), notes: [{ id: 'n1', text: 123 }], or notes: [{ id: 'n1', text: { body: 'hi' } }] passed to patch_flow_json/set_flow_json.

Common situations: An LLM agent puts rich structured content (markdown object, array of lines) into text; a caller passes null for empty notes instead of ''; numeric or boolean content was passed unconverted.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

	// 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>
			if (
				typeof p !== 'object' ||
				Array.isArray(n.position) ||
				typeof p.x !== 'number' ||

View on GitHub (pinned to e474e8803c)