windmill-labs/windmill · error · Error

Invalid note at index ${index}: contained_node_ids "${id}" d

Error message

Invalid note at index ${index}: contained_node_ids "${id}" does not match any flow module

What it means

After the array-of-strings check, validateFlowNotes cross-references each id in a group note's contained_node_ids against the set of module ids actually present in the flow (when a moduleIds set was supplied). This error is thrown when an id refers to a module that does not exist in the flow, preventing dangling group memberships.

Source

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

			) {
				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
		} as FlowNote

		// Free notes need explicit geometry to be draggable/resizable. Size first
		// (from text, so tall notes get a tall box), then place any note missing a
		// position to the left of the flow column, stacking auto-placed notes by
		// their real heights so several generated notes don't overlap. Group notes
		// derive their layout from contained nodes, so they are left alone.

View on GitHub (pinned to e474e8803c)

Solutions

  1. Use the exact module ids returned by the module-listing/read tool output, not invented names.
  2. Re-read the current flow modules and rebuild contained_node_ids from them.
  3. Remove the offending id from contained_node_ids, or drop the field to place an unanchored group.
  4. If creating modules and notes in one call, order the calls so modules exist before the group note references them.

Example fix

// before
contained_node_ids: ['step_1'] // not a real module id
// after
contained_node_ids: ['9f2c...'] // id taken from the flow's actual module list
Defensive patterns

Strategy: validation

Validate before calling

const known = new Set(modules.map((m) => m.id))
for (const n of notes) {
  for (const id of n.contained_node_ids ?? []) {
    if (!known.has(id)) throw new Error(`unknown module id: ${id}`)
  }
}

Type guard

function referencesKnownModules(ids: unknown, moduleIds: Set<string>): ids is string[] {
  return Array.isArray(ids) && ids.every((id) => typeof id === 'string' && moduleIds.has(id))
}

Try / catch

try {
  validateFlowNotes(notes, moduleIds)
} catch (e) {
  if (e.message.includes('does not match any flow module')) {
    console.warn('filtering unknown group ids');
    notes = notes.map((n) => ({ ...n, contained_node_ids: (n.contained_node_ids ?? []).filter((id) => moduleIds.has(id)) }))
  }
}

Prevention

When it happens

Trigger: A group note lists contained_node_ids like ["step_1"] but the flow's modules are keyed differently (e.g. "a1b2c3" uuids), the module was renamed/removed before the note call, or the LLM hallucinated an id.

Common situations: Model invents human-readable ids instead of using real module ids from earlier tool output; the flow was regenerated between the module listing and the notes call; stale ids copied from a previous flow version.

Related errors


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