n8n-io/n8n · warning · Error
Reflector output did not contain a JSON object
Error message
Reflector output did not contain a JSON object
What it means
Warning thrown by the unknown-config-keys validator when a node's config object has top-level keys that are not recognised NodeConfig fields. Unrecognised keys are silently dropped during serialization, which leaves the node with empty parameters — a common LLM/MCP-client mistake of placing parameters directly under config instead of config.parameters.
Source
Thrown at packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts:282
if (hasAncestorIn(id, ownActionIds, activeById)) return false;
if (hasAncestorIn(id, allSameKindActionIds, activeById)) return true;
return !hasAncestorIn(id, otherRemovalIds, activeById);
}
function normalizeReplacementParentId(
parentId: string | null | undefined,
activeById: Map<string, ObservationLogEntry>,
removedIds: Set<string>,
): string | null | undefined {
if (parentId === undefined || parentId === null) return parentId;
return activeById.has(parentId) && !removedIds.has(parentId) ? parentId : null;
}
function extractJsonObject(output: string): string {
const start = output.indexOf('{');
const end = output.lastIndexOf('}');
if (start === -1 || end === -1 || end < start) {
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');View on GitHub (pinned to 5ac6606e81)
Solutions
- Move the unknown keys inside config.parameters so they survive serialization.
- If a key is a legitimate new NodeConfig field, add it to the KNOWN_CONFIG_KEYS map (and to the NodeConfig type).
- Prefix truly internal markers with '_' so they are intentionally ignored.
Example fix
// before — url sits at the top level of config and gets dropped
httpRequest({ name: 'Get', url: 'https://example.com' });
// after
httpRequest({ name: 'Get', parameters: { url: 'https://example.com' } }); Defensive patterns
Strategy: validation
Validate before calling
const KNOWN_CONFIG_KEYS = new Set([
'parameters', 'credentials', 'name', 'position', 'webhookId', 'disabled', 'notes',
'notesInFlow', 'executeOnce', 'retryOnFail', 'maxTries', 'waitBetweenTries',
'alwaysOutputData', 'onError', 'extendsCredential', 'pinData', 'output', 'subnodes',
]);
function findUnknownConfigKeys(config: Record<string, unknown>): string[] {
return Object.keys(config).filter((k) => !KNOWN_CONFIG_KEYS.has(k) && !k.startsWith('_'));
}
const unknown = findUnknownConfigKeys(node.config);
if (unknown.length) throw new Error(`Move into config.parameters: ${unknown.join(', ')}`); Prevention
- Always nest node inputs under config.parameters, never at the top level of config.
- For internal markers, prefix keys with '_' so they are intentionally ignored.
- When adding a real NodeConfig field, update KNOWN_CONFIG_KEYS and the NodeConfig type together.
When it happens
Trigger: Object.keys(node.config) includes any key not in KNOWN_CONFIG_KEYS (parameters, credentials, name, position, webhookId, disabled, notes, notesInFlow, executeOnce, retryOnFail, maxTries, waitBetweenTries, alwaysOutputData, onError, extendsCredential, pinData, output, subnodes) and not starting with '_'.
Common situations: An LLM puts node inputs at the top level (e.g. config.url instead of config.parameters.url); a migration that nested the schema; copying an example that predated the NodeConfig contract.
Related errors
- LangSmithTelemetry creates its own tracer — do not use .otlp
- No suspended run found for runId: ${this.runId}
- Agent run was aborted
- ${turn.errorReason.message}
- Tool name "${RECALL_MEMORY_TOOL_NAME}" is reserved while epi
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/0091108484cbdfe0.
Report an issue: GitHub.