n8n-io/n8n · warning · Error

Reflector output must be a JSON object

Error message

Reflector output must be a JSON object

What it means

Warning thrown by the tool-node validator when a tool node has no parameters set. Most tool nodes need at least one parameter (often a $fromAI description) to be useful to an agent. A small allowlist of tools (calculator, vector stores, MCP client tool, Wikipedia, SerpApi) is exempt.

Source

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

export type RunObservationLogReflectorResult =
	| { status: 'skipped'; reason: 'below-threshold'; tokenCount: number }
	| {
			status: 'ran';
			tokenCount: number;
			remainingTokenCount: number;
			overBudgetAfterReflection: boolean;
			reflection: ObservationLogReflection;
			result: ObservationLogReflectionResult;
	  };

export function parseObservationLogReflectionJson(output: string): ObservationLogReflection {
	let parsed: unknown;
	try {
		parsed = JSON.parse(extractJsonObject(output));
	} catch {
		throw new Error('Reflector output must be valid JSON');
	}
	if (!isRecord(parsed)) throw new Error('Reflector output must be a JSON object');

	return {
		drop: readStringArray(parsed.drop ?? [], 'drop'),
		merge: readMergeArray(parsed.merge ?? []),
	};
}

export function renderObservationLogForReflection(entries: ObservationLogEntry[]): string {
	const activeEntries = entries.filter((entry) => entry.status === 'active').sort(compareEntries);
	const activeIds = new Set(activeEntries.map((entry) => entry.id));
	const childrenByParent = new Map<string, ObservationLogEntry[]>();
	const roots: ObservationLogEntry[] = [];

	for (const entry of activeEntries) {
		if (entry.parentId && activeIds.has(entry.parentId)) {
			const children = childrenByParent.get(entry.parentId) ?? [];
			children.push(entry);
			childrenByParent.set(entry.parentId, children);

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Add the parameters the tool needs (e.g. a $fromAI description for each agent-provided argument).
  2. If the tool genuinely takes no parameters, register it in the node type with an empty properties array so the provider path skips validation.
  3. Confirm node.type is correct — a misnamed type that happens to contain 'tool' will trigger this check.

Example fix

// before
const t = toolHttp({ name: 'Fetcher' });

// after — add a parameter the agent can supply
toolHttp({
  name: 'Fetcher',
  parameters: { url: expr('={{ $fromAI.url }}') },
});
Defensive patterns

Strategy: validation

Validate before calling

const TOOLS_WITHOUT_PARAMETERS = new Set([
  '@n8n/n8n-nodes-langchain.toolCalculator',
  '@n8n/n8n-nodes-langchain.toolVectorStore',
  '@n8n/n8n-nodes-langchain.vectorStoreInMemory',
  '@n8n/n8n-nodes-langchain.mcpClientTool',
  '@n8n/n8n-nodes-langchain.toolWikipedia',
  '@n8n/n8n-nodes-langchain.toolSerpApi',
]);

function needsParameters(type: string, params: unknown, propertiesLength: number | undefined): boolean {
  if (!/tool/i.test(type)) return false;
  if (TOOLS_WITHOUT_PARAMETERS.has(type)) return false;
  if (propertiesLength === 0) return false;
  return !params || Object.keys(params as object).length === 0;
}

Prevention

When it happens

Trigger: isToolNode(node.type) is true (type contains 'tool'/'Tool'), the node is not in TOOLS_WITHOUT_PARAMETERS, the nodeTypesProvider (if present) does not report an empty properties array, and node.config.parameters is missing or has zero own keys.

Common situations: Adding a custom HTTP/tool node and forgetting its description parameters; an AI builder emits a tool shell without $fromAI fields; the provider info was unavailable so the static allowlist did not cover a parameterless custom tool.

Related errors


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