n8n-io/n8n · error · Error

Reflector field "merge" must be an array

Error message

Reflector field "merge" must be an array

What it means

The reflector JSON contract requires a top-level `merge` field that is an array of merge-instructions. `readMergeArray` throws this when `parsed.merge` is present but not an array (e.g. an object or a string). Note that `parseObservationLogReflectionJson` defaults `merge` to `[]` when absent, so this only fires when the field exists but has the wrong type.

Source

Thrown at packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts:300

		throw new Error('Reflector output did not contain a JSON object');
	}
	return output.slice(start, end + 1);
}

function readStringArray(value: unknown, fieldName: string): string[] {
	if (!Array.isArray(value)) throw new Error(`Reflector field "${fieldName}" must be an array`);
	const strings: string[] = [];
	for (const item of value) {
		if (typeof item !== 'string') {
			throw new Error(`Reflector field "${fieldName}" must contain only strings`);
		}
		strings.push(item);
	}
	return strings;
}

function readMergeArray(value: unknown): ObservationLogMerge[] {
	if (!Array.isArray(value)) throw new Error('Reflector field "merge" must be an array');
	return value.map(readMerge);
}

function readMerge(value: unknown, index: number): ObservationLogMerge {
	if (!isRecord(value)) throw new Error(`Reflector merge[${index}] must be an object`);
	const supersedes = readStringArray(value.supersedes, `merge[${index}].supersedes`);
	const marker = readMarker(value.marker, index);
	if (typeof value.text !== 'string') {
		throw new Error(`Reflector merge[${index}].text must be a string`);
	}

	const parentId = readOptionalParentId(value.parentId, index);
	return {
		supersedes,
		marker,
		text: value.text,
		...(parentId !== undefined && { parentId }),
	};

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Log the raw reflector output to confirm `merge` is a non-array type.
  2. Update the reflector system prompt to show `merge` as an array with at least one example element, emphasizing the brackets.
  3. If the model consistently returns a single object, add a pre-parse normalization step that wraps a lone object into `[object]` before validation.
  4. Switch to a model or mode that supports structured/JSON-schema-constrained output.

Example fix

// before: { "drop": [], "merge": { "supersedes": ["obs-1"], "marker": "INFO", "text": "..." } }
// after:  { "drop": [], "merge": [ { "supersedes": ["obs-1"], "marker": "INFO", "text": "..." } ] }
Defensive patterns

Strategy: validation

Validate before calling

const raw = JSON.parse(extractJsonObject(output));
if (raw.merge !== undefined && !Array.isArray(raw.merge)) {
  // Normalize: wrap single object in array
  if (typeof raw.merge === 'object' && raw.merge !== null) {
    raw.merge = [raw.merge];
  } else {
    raw.merge = [];
  }
}

Type guard

function isMergeArray(value: unknown): value is unknown[] {
  return Array.isArray(value);
}

Try / catch

try {
  const reflection = parseObservationLogReflectionJson(output);
} catch (e) {
  logger.warn('Reflector merge field not an array', { output });
}

Prevention

When it happens

Trigger: The reflector LLM returns `{ "merge": { "supersedes": [], "marker": "INFO", "text": "..." } }` (a single object instead of an array) or `{ "merge": "" }`.

Common situations: The model emitted a single merge object rather than wrapping it in an array. The prompt example showed merge as an object, causing the model to mirror that shape. A model with poor array-handling collapsed the array.

Related errors


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