windmill-labs/windmill · error · Error

Invalid note at index ${index}: contained_node_ids must be a

Error message

Invalid note at index ${index}: contained_node_ids must be an array of strings

What it means

For notes of type "group", validateFlowNotes optionally accepts `contained_node_ids`, the list of flow module ids the group visually wraps. When the field is provided it must be an array whose every element is a string; otherwise this error is thrown.

Source

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

		if (n.size !== undefined && n.size !== null) {
			const s = n.size as Record<string, unknown>
			if (
				typeof s !== 'object' ||
				Array.isArray(n.size) ||
				typeof s.width !== 'number' ||
				typeof s.height !== 'number'
			) {
				throw new Error(
					`Invalid note at index ${index}: size must be an object with numeric width and height`
				)
			}
		}
		if (type === 'group' && n.contained_node_ids !== undefined) {
			if (
				!Array.isArray(n.contained_node_ids) ||
				n.contained_node_ids.some((id) => typeof id !== 'string')
			) {
				throw new Error(
					`Invalid note at index ${index}: contained_node_ids must be an array of strings`
				)
			}
			if (moduleIds) {
				for (const id of n.contained_node_ids as string[]) {
					if (!moduleIds.has(id)) {
						throw new Error(
							`Invalid note at index ${index}: contained_node_ids "${id}" does not match any flow module`
						)
					}
				}
			}
		}
		const normalized = {
			...(n as FlowNote),
			type,
			// Preserve a provided color; only seed the default when omitted.
			color: typeof n.color === 'string' ? n.color : DEFAULT_NOTE_COLOR

View on GitHub (pinned to e474e8803c)

Solutions

  1. Ensure contained_node_ids is an array of strings: ["mod1", "mod2"].
  2. Map module objects to their ids: contained_node_ids: modules.map((m) => m.id).
  3. Omit contained_node_ids to let the group be defined without explicit membership.
  4. Verify the note type is "group" — this check only runs for group notes.

Example fix

// before
{ id: 'g1', type: 'group', text: 'step', contained_node_ids: 'a,b' }
// after
{ id: 'g1', type: 'group', text: 'step', contained_node_ids: ['a', 'b'] }
Defensive patterns

Strategy: type-guard

Validate before calling

if (note.type === 'group' && note.contained_node_ids !== undefined) {
  const ok = Array.isArray(note.contained_node_ids) &&
    note.contained_node_ids.every((id) => typeof id === 'string')
  if (!ok) throw new Error('contained_node_ids must be string[]')
}

Type guard

function isStringArray(v): v is string[] {
  return Array.isArray(v) && v.every((x) => typeof x === 'string')
}

Try / catch

try {
  validateFlowNotes(notes, moduleIds)
} catch (e) {
  if (e.message.includes('contained_node_ids')) {
    notes = notes.map((n) => ('contained_node_ids' in n ? { ...n, contained_node_ids: String(n.contained_node_ids).split(',') } : n))
  }
}

Prevention

When it happens

Trigger: Calling the note/flowTools tool with a type:"group" note whose contained_node_ids is a single string "a,b", a comma-split array containing non-strings like ["a", 42], or an object instead of an array.

Common situations: The LLM joins ids into one comma-separated string; ids get mixed with numeric indexes from a table; the caller passes the raw module list (objects) instead of the id strings.

Related errors


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