{"record":{"id":"ee386e8b0edf9e29","repo":"n8n-io/n8n","slug":"reflector-field-fieldname-must-be-an-array","errorCode":null,"errorMessage":"Reflector field \"${fieldName}\" must be an array","messagePattern":"Reflector field \"(.+?)\" must be an array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts","lineNumber":288,"sourceCode":"\tparentId: string | null | undefined,\n\tactiveById: Map<string, ObservationLogEntry>,\n\tremovedIds: Set<string>,\n): string | null | undefined {\n\tif (parentId === undefined || parentId === null) return parentId;\n\treturn activeById.has(parentId) && !removedIds.has(parentId) ? parentId : null;\n}\n\nfunction extractJsonObject(output: string): string {\n\tconst start = output.indexOf('{');\n\tconst end = output.lastIndexOf('}');\n\tif (start === -1 || end === -1 || end < start) {\n\t\tthrow new Error('Reflector output did not contain a JSON object');\n\t}\n\treturn output.slice(start, end + 1);\n}\n\nfunction readStringArray(value: unknown, fieldName: string): string[] {\n\tif (!Array.isArray(value)) throw new Error(`Reflector field \"${fieldName}\" must be an array`);\n\tconst strings: string[] = [];\n\tfor (const item of value) {\n\t\tif (typeof item !== 'string') {\n\t\t\tthrow new Error(`Reflector field \"${fieldName}\" must contain only strings`);\n\t\t}\n\t\tstrings.push(item);\n\t}\n\treturn strings;\n}\n\nfunction readMergeArray(value: unknown): ObservationLogMerge[] {\n\tif (!Array.isArray(value)) throw new Error('Reflector field \"merge\" must be an array');\n\treturn value.map(readMerge);\n}\n\nfunction readMerge(value: unknown, index: number): ObservationLogMerge {\n\tif (!isRecord(value)) throw new Error(`Reflector merge[${index}] must be an object`);\n\tconst supersedes = readStringArray(value.supersedes, `merge[${index}].supersedes`);","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts#L270-L306","documentation":"The observation-log reflector asks an LLM to return a JSON object with `drop` (string[]) and `merge` (array) fields. `readStringArray` validates that a field the reflector contract requires to be a string array is actually an array. This fires when the LLM emits a non-array value (object, string, number, null) for a field the parser expects to be `string[]`.","triggerScenarios":"The LLM-driven `reflect()` call returns JSON where a field expected to be `string[]` (e.g. `drop`, or `merge[i].supersedes`) is not an array — for example `{ \"drop\": \"id1,id2\" }` (a comma-separated string instead of an array) or `{ \"drop\": null }`.","commonSituations":"The reflector prompt was modified or weakened so the model stops emitting arrays. A cheaper/different model that follows the JSON schema less reliably was swapped in. The LLM wrapped the array in an extra object layer.","solutions":["Inspect the raw reflector LLM `output` string (log it before parse) to see what the model actually returned for the field named in the error.","Strengthen the reflector system prompt to reiterate that `drop` and `merge[i].supersedes` must be JSON arrays of strings, not comma-delimited strings or objects.","If using a weaker model, switch to one with stronger structured-output / JSON-mode support, or enable the provider's JSON response format if available.","As a defensive measure, pre-normalize known-shape violations (e.g. split comma strings into arrays) before calling `parseObservationLogReflectionJson`, though fixing the prompt is preferred."],"exampleFix":"// before: LLM returns { \"drop\": \"obs-1,obs-2\" }\n// Strengthen the reflector prompt:\n/*\nYou MUST return JSON matching exactly this shape:\n{\n  \"drop\": [\"id1\", \"id2\"],   // array of strings, NEVER a comma-separated string\n  \"merge\": [...]\n}\n*/\n\n// after: LLM returns { \"drop\": [\"obs-1\", \"obs-2\"] }","handlingStrategy":"validation","validationCode":"// Validate reflector LLM output shape before parsing\nfunction isValidReflectionShape(raw: unknown): raw is { drop: unknown[]; merge: unknown[] } {\n  return typeof raw === 'object' && raw !== null &&\n    Array.isArray((raw as any).drop) && Array.isArray((raw as any).merge);\n}\n\n// Usage:\nconst json = JSON.parse(extractJsonObject(output));\nif (!isValidReflectionShape(json)) {\n  throw new Error('Reflector output shape invalid: drop and merge must be arrays');\n}","typeGuard":"function isStringArray(value: unknown): value is string[] {\n  return Array.isArray(value) && value.every((item) => typeof item === 'string');\n}","tryCatchPattern":"try {\n  const reflection = parseObservationLogReflectionJson(output);\n} catch (e) {\n  // Reflector LLM produced malformed JSON — log raw output and skip this reflection cycle\n  logger.warn('Reflection parse failed, skipping cycle', { output, error: (e as Error).message });\n}","preventionTips":["Use a provider that supports structured-output / JSON-schema-constrained decoding for the reflector LLM call.","Log the raw reflector output string on every parse failure for debugging.","Treat reflection as best-effort: catch parse errors and skip the cycle rather than crashing the agent run.","Pin the reflector model to one known to follow JSON schemas reliably."],"tags":["llm-output-validation","observation-log","json-parsing","reflection"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}