n8n-io/n8n · error · Error
Reflector merge[${index}].marker must be a known observation
Error message
Reflector merge[${index}].marker must be a known observation marker What it means
`readMarker` is called for each merge object and first checks that `value.marker` is a string. This throw (line 323) fires when `marker` is not a string at all — it is missing, null, a number, etc. The marker classifies the merged observation's severity. A separate throw at line 336 covers the case where the string is not one of the four known values.
Source
Thrown at packages/@n8n/agents/src/runtime/memory/observation-log-reflector.ts:323
if (!isRecord(value)) throw new Error(`Reflector merge[${index}] must be an object`);
const supersedes = readStringArray(value.supersedes, `merge[${index}].supersedes`);
const marker = readMarker(value.marker, index);
if (typeof value.text !== 'string') {
throw new Error(`Reflector merge[${index}].text must be a string`);
}
const parentId = readOptionalParentId(value.parentId, index);
return {
supersedes,
marker,
text: value.text,
...(parentId !== undefined && { parentId }),
};
}
function readMarker(value: unknown, index: number): ObservationLogMarker {
if (typeof value !== 'string') {
throw new Error(`Reflector merge[${index}].marker must be a known observation marker`);
}
switch (value.toUpperCase()) {
case 'CRITICAL':
return 'critical';
case 'IMPORTANT':
return 'important';
case 'INFO':
return 'info';
case 'COMPLETION':
return 'completion';
default:
throw new Error(`Reflector merge[${index}].marker must be a known observation marker`);
}
}
function readOptionalParentId(value: unknown, index: number): string | null | undefined {
if (value === undefined) return undefined;View on GitHub (pinned to 5ac6606e81)
Solutions
- Use the `index` from the error to find the merge object whose `marker` is non-string.
- Update the reflector prompt: `marker` is required and must be one of `CRITICAL`, `IMPORTANT`, `INFO`, `COMPLETION` (case-insensitive).
- If you want `marker` to be optional, patch the reader to default to `'info'` when `marker` is absent — but coordinate with the memory system's expectations.
- Enable structured-output / JSON-schema mode to enforce `marker: string` at the API level.
Example fix
// before: { "merge": [{ "supersedes": ["obs-1"], "marker": null, "text": "..." }] }
// after: { "merge": [{ "supersedes": ["obs-1"], "marker": "INFO", "text": "..." }] } Defensive patterns
Strategy: validation
Validate before calling
const raw = JSON.parse(extractJsonObject(output));
if (Array.isArray(raw.merge)) {
for (const m of raw.merge) {
if (isRecord(m) && typeof m.marker !== 'string') {
m.marker = 'INFO'; // default to info when missing/non-string
}
}
} Type guard
function isStringMarker(value: unknown): value is string {
return typeof value === 'string';
} Try / catch
try {
const reflection = parseObservationLogReflectionJson(output);
} catch (e) {
logger.warn('Reflector marker not a string', { index: 'see message', output });
} Prevention
- Default `marker` to `'INFO'` in a pre-normalization step if you want it optional.
- List the four allowed markers prominently in the reflector prompt.
- Use structured-output mode with `marker: string` constraint.
When it happens
Trigger: The reflector LLM returns a merge object with `{ "marker": null }`, `{ "marker": 1 }`, or omits `marker` entirely (so it is `undefined`).
Common situations: The reflector prompt does not list `marker` as required. The model used a non-string sentinel. The model omitted the field for merges it considered low priority.
Related errors
- Reflector field "${fieldName}" must contain only strings
- Reflector merge[${index}] must be an object
- Reflector merge[${index}].text must be a string
- Reflector merge[${index}].parentId must be a string or null
- Reflector field "${fieldName}" must be an array
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/6996256e16db7156.
Report an issue: GitHub.