{"record":{"id":"d0cb0df97b076f2f","repo":"n8n-io/n8n","slug":"reflector-output-must-be-valid-json","errorCode":null,"errorMessage":"Reflector output must be valid JSON","messagePattern":"Reflector output must be valid JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts","lineNumber":69,"sourceCode":"}\n\nexport type RunObservationLogReflectorResult =\n\t| { status: 'skipped'; reason: 'below-threshold'; tokenCount: number }\n\t| {\n\t\t\tstatus: 'ran';\n\t\t\ttokenCount: number;\n\t\t\tremainingTokenCount: number;\n\t\t\toverBudgetAfterReflection: boolean;\n\t\t\treflection: ObservationLogReflection;\n\t\t\tresult: ObservationLogReflectionResult;\n\t  };\n\nexport function parseObservationLogReflectionJson(output: string): ObservationLogReflection {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(extractJsonObject(output));\n\t} catch {\n\t\tthrow new Error('Reflector output must be valid JSON');\n\t}\n\tif (!isRecord(parsed)) throw new Error('Reflector output must be a JSON object');\n\n\treturn {\n\t\tdrop: readStringArray(parsed.drop ?? [], 'drop'),\n\t\tmerge: readMergeArray(parsed.merge ?? []),\n\t};\n}\n\nexport function renderObservationLogForReflection(entries: ObservationLogEntry[]): string {\n\tconst activeEntries = entries.filter((entry) => entry.status === 'active').sort(compareEntries);\n\tconst activeIds = new Set(activeEntries.map((entry) => entry.id));\n\tconst childrenByParent = new Map<string, ObservationLogEntry[]>();\n\tconst roots: ObservationLogEntry[] = [];\n\n\tfor (const entry of activeEntries) {\n\t\tif (entry.parentId && activeIds.has(entry.parentId)) {\n\t\t\tconst children = childrenByParent.get(entry.parentId) ?? [];","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts#L51-L87","documentation":"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.).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — model created standalone, never attached\nconst lm = chatOpenAi({ name: 'GPT' });\nconst emb = embeddingsOpenAi({ name: 'Embed' });\n\n// after\nconst agentNode = agent({\n  name: 'Agent',\n  languageModel: chatOpenAi({ name: 'GPT' }),\n});","handlingStrategy":"validation","validationCode":"function assertSubnodeAttached(type: string, hasAiConnection: boolean, subnodeInfo: { subnodeField: string } | null): void {\n  if (subnodeInfo && !hasAiConnection) {\n    throw new Error(`${type} must be attached to a parent as ${subnodeInfo.subnodeField}`);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["ai","langchain","subnode","connections","workflow-structure"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}