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

  1. Make parameters.assignments.assignments an array of assignment objects.
  2. If the inner list is genuinely empty, use assignments: { assignments: [] }.
  3. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/b708bc599acfb7fd. Report an issue: GitHub.