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_COLORView on GitHub (pinned to e474e8803c)
Solutions
- Ensure contained_node_ids is an array of strings: ["mod1", "mod2"].
- Map module objects to their ids: contained_node_ids: modules.map((m) => m.id).
- Omit contained_node_ids to let the group be defined without explicit membership.
- 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
- Pass ids as an array of strings, never a joined string
- Map module objects to their id strings before building the note
- Only set contained_node_ids on notes with type "group"
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
- Invalid note at index ${index}: position must be an object w
- Invalid note at index ${index}: size must be an object with
- Invalid flow modules:\n${errors.join('\n')}
- Invalid failure_module: only "rawscript" and "script" module
- Flow notes must be an array
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/4d969a6013d76927.
Report an issue: GitHub.