n8n-io/n8n · error · Error
${turn.errorReason.message}
Error message
${turn.errorReason.message} What it means
Thrown by the Set node validator (v3.3+) when parameters.assignments is a valid object but its inner assignments field is present and is not an array. The inner field must be the list of assignment entries.
Source
Thrown at packages/@n8n/agents/src/runtime/loop/agent-runtime.ts:923
// Fold the just-finished turn's usage in before the abort check so a
// stop that lands right after the model call still bills its tokens.
totalUsage = mergeUsage(totalUsage, turn.usage);
incrementTokenCountFromUsage(options?.executionCounter, turn.usage);
sink.reportUsage(totalUsage);
this.assertNotAborted(abortScope);
lastFinishReason = turn.finishReason;
list.addResponse(turn.newMessages);
// The turn is now in the list; drop any retained streamed text so a later
// abort's snapshot can't duplicate it (a stop before this point recovers it).
sink.onTurnFolded?.();
if (turn.aiFinishReason !== 'tool-calls') {
// A rejected/filtered request (e.g. a provider prompt safety block)
// surfaces as an output-less turn instead of an SDK error — throw so
// the failure reaches the caller rather than ending the run silently.
if (turn.errorReason) throw new Error(turn.errorReason.message);
structuredOutput = turn.structuredOutput;
this.emitTurnEnd(turn.newMessages, extractSettledToolCalls(turn.newMessages));
reachedStopCondition = true;
break;
}
const batch = await this.toolExecutor.iterateToolCallsConcurrent({
...buildToolBatchContext(toolMap),
toolCalls: turn.toolCalls,
});
const finalized = await finishToolBatch(batch, toolMap, iterationCount + 1);
if (finalized.suspended) return finalized.result;
// Emit TurnEnd after all tool calls in this iteration are processed
this.emitTurnEnd(turn.newMessages, extractSettledToolCalls(list.responseDelta()));
// Step boundary reached with nothing pending: durably checkpoint so a
// crash before the next model call loses only the in-flight step.View on GitHub (pinned to 5ac6606e81)
Solutions
- Make parameters.assignments.assignments an array of assignment objects.
- If the inner list is genuinely empty, use assignments: { assignments: [] }.
- Omit the inner assignments key entirely if you only want the outer placeholder (undefined is tolerated).
Example fix
// before
set({
name: 'Set X',
assignments: { assignments: { id: '1', name: 'x', value: 1, type: 'number' } },
});
// after
set({
name: 'Set X',
assignments: {
assignments: [{ id: '1', name: 'x', value: 1, type: 'number' }],
},
}); Defensive patterns
Strategy: validation
Validate before calling
function assertAssignmentsArray(assignments: unknown): void {
if (assignments !== undefined && !Array.isArray(assignments)) {
throw new Error('parameters.assignments.assignments must be an array');
}
}
// before building:
assertAssignmentsArray(params.assignments?.assignments); Type guard
function isAssignmentList(v: unknown): v is unknown[] {
return Array.isArray(v);
} Prevention
- Use a typed assignment builder that always produces an array for the inner field.
- Avoid double-nesting the assignments object.
- Omit the inner key rather than passing a non-array.
When it happens
Trigger: params.assignments is a record AND assignments.assignments !== undefined AND !Array.isArray(assignments.assignments) — e.g. it is an object, string, or number.
Common situations: Nesting one level too deep (assignments.assignments = {...}); an AI builder mirrors the outer object shape inside; a typo turning the array into an object.
Related errors
- Agent run was aborted
- Tool name "${RECALL_MEMORY_TOOL_NAME}" is reserved while epi
- MCP server "${this.config.name}": connectionTimeoutMs must b
- MCP server "${config.name}": provide either "url" or "comman
- Episodic memory requires a resolved embedding model before r
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/b708bc599acfb7fd.
Report an issue: GitHub.