windmill-labs/windmill · error · Error

Invalid note at index ${index}: must be an object

Error message

Invalid note at index ${index}: must be an object

What it means

Each entry of the notes array must be a non-null, non-array object. validateFlowNotes throws this error when an element is null, a primitive, or an array, because FlowNote requires named fields (id, text, etc.) that only an object can carry.

Source

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

	if (rawNotes == null) {
		return null
	}

	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)) {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Make each array element a plain object with at least id and text fields
  2. Remove null/empty placeholder entries from the array
  3. If data is tabular, convert each row to an object before passing

Example fix

// before
notes: ['step 1 done', 'review']
// after
notes: [{ id: 'n1', text: 'step 1 done' }, { id: 'n2', text: 'review' }]
Defensive patterns

Strategy: type-guard

Validate before calling

notes.forEach((n, i) => {
  if (n === null || typeof n !== 'object' || Array.isArray(n)) throw new Error(`notes[${i}] must be an object`);
});

Type guard

function isNoteObject(n: unknown): n is Record<string, unknown> {
  return n !== null && typeof n === 'object' && !Array.isArray(n);
}

Try / catch

try {
  validateFlowNotes(rawNotes, moduleIds);
} catch (e) {
  if (e instanceof Error && e.message.includes('must be an object')) {
    console.error('A notes array element is not an object — sanitize the list and retry');
  } else throw e;
}

Prevention

When it happens

Trigger: notes: [null], notes: ['a note'], notes: [['n1', 'text']] passed to patch_flow_json/set_flow_json.

Common situations: An LLM agent serializes notes as CSV-like arrays; a null placeholder slipped into the list; a stringified note was double-encoded and ended up a string element.

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/29c4e4284a5f7942. Report an issue: GitHub.