n8n-io/n8n · error · Error
Reflector output must be valid JSON
Error message
Reflector output must be valid JSON
What it means
Thrown by the subnode-connection validator when a node type that is only valid as a subnode (e.g. an embedding or language model) has no AI connection to a parent node. Such types must be attached via the parent's subnodes config (embedding(), languageModel(), etc.).
Source
Thrown at packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts:69
}
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) ?? [];View on GitHub (pinned to 5ac6606e81)
Solutions
- Attach the node via the parent's subnode factory, e.g. agent({ ..., languageModel: lm }) or agent({ ..., embedding: emb }).
- Use the dedicated factory functions (languageModel(), embedding()) and wire them into the parent's subnodes config.
- Remove the orphan subnode if it is no longer needed.
Example fix
// before — model created standalone, never attached
const lm = chatOpenAi({ name: 'GPT' });
const emb = embeddingsOpenAi({ name: 'Embed' });
// after
const agentNode = agent({
name: 'Agent',
languageModel: chatOpenAi({ name: 'GPT' }),
}); Defensive patterns
Strategy: validation
Validate before calling
function assertSubnodeAttached(type: string, hasAiConnection: boolean, subnodeInfo: { subnodeField: string } | null): void {
if (subnodeInfo && !hasAiConnection) {
throw new Error(`${type} must be attached to a parent as ${subnodeInfo.subnodeField}`);
}
} Prevention
- Always create subnodes through the parent's subnode factories (languageModel(), embedding()).
- Check the node type's required subnode info before emitting standalone nodes.
- Remove orphan subnodes when their parent is deleted.
When it happens
Trigger: getRequiredSubnodeInfo(graphNode.instance.type) returns a subnodeInfo (connectionType + subnodeField), and hasAiConnectionOfType(graphNode, subnodeInfo.connectionType) is false — i.e. the node has no AI-side connection of the required type to a parent.
Common situations: Adding a Chat OpenAI / Embeddings model as a top-level node instead of attaching it; an AI builder creates the model but forgets the parent's subnodes config; deleting the parent left an orphan subnode.
Related errors
- Run ${this.runId} is not suspended. Cannot resume.
- No checkpoint found for runId: ${this.runId}
- Reflector output must be a JSON object
- No models connected
- No tool inputs found
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/d0cb0df97b076f2f.
Report an issue: GitHub.