mastra-ai/mastra · error · MastraError
PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST
PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST
Error message
Processor ${processor.id} returned a MessageList instance other than the one that was passed in as an argument. New external message list instances are not supported. Use the messageList argument instead. What it means
Thrown when a processor's processOutputResult returns a brand-new MessageList instance instead of mutating the messageList argument it was given. The runner only accepts the same instance so recorded mutations can be reconciled; returning a foreign MessageList would silently discard the runner's mutation recording. Category USER, domain AGENT, id PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST.
Source
Thrown at packages/core/src/processors/runner.ts:739
messageList,
state: processorState.customState,
result: result ?? defaultResult,
abort,
agent: this.agent,
...createObservabilityContext({ currentSpan: processorSpan }),
requestContext,
retryCount,
writer,
sendSignal: createProcessorSendSignal({ messageList, writer }),
});
// Stop recording and get mutations for this processor
const mutations = messageList.stopRecording();
// Handle the new return type - MessageList or MastraDBMessage[]
if (processResult instanceof MessageList) {
if (processResult !== messageList) {
throw new MastraError({
category: 'USER',
domain: 'AGENT',
id: 'PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST',
text: `Processor ${processor.id} returned a MessageList instance other than the one that was passed in as an argument. New external message list instances are not supported. Use the messageList argument instead.`,
});
}
if (mutations.length > 0) {
processableMessages = getProcessableResponseMessages(processResult);
}
} else {
if (processResult) {
const deletedIds = idsBeforeProcessing.filter(
(i: string) => !processResult.some((m: MastraDBMessage) => m.id === i),
);
if (deletedIds.length) {
messageList.removeByIds(deletedIds);
}
processableMessages = processResult || [];View on GitHub (pinned to 75dd419e61)
Solutions
- Mutate the passed-in messageList in place (its add/remove/update APIs) and return it (or return nothing/arrays per the supported signatures).
- If you need filtering, remove messages from the existing messageList rather than building a new one.
- If you truly need a fresh list, do it outside the processor or restructure so the runner-owned list stays canonical.
Example fix
// before
processOutputResult: async ({ messageList }) => {
const filtered = new MessageList();
for (const m of messageList.get.all()) if (!isBlocked(m)) filtered.add(m);
return filtered;
}
// after
processOutputResult: async ({ messageList }) => {
for (const m of messageList.get.all()) if (isBlocked(m)) messageList.remove(m);
return messageList;
} Defensive patterns
Strategy: validation
Validate before calling
// Enforce in code review / wrapper:
function assertSameMessageList(returned: unknown, expected: MessageList) {
if (returned instanceof MessageList && returned !== expected)
throw new Error('Processor must return the SAME messageList instance it received');
} Type guard
function returnsForeignList<T extends MessageList>(ret: T, arg: MessageList): boolean {
return ret instanceof MessageList && ret !== arg;
} Try / catch
try {
await agent.generate(input);
} catch (e) {
if (e instanceof MastraError && e.id === 'PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST') {
const processorId = /Processor (.+?) returned/.exec(e.message)?.[1];
console.error(`Fix processor ${processorId}: mutate the given messageList, do not return a new one`);
} else throw e;
} Prevention
- Never construct `new MessageList()` inside processOutputResult/processInput
- Use the messageList argument's mutation APIs (add/remove/update/clear)
- When migrating from array-return processors, return the same list or plain arrays, not new lists
- Add a unit test asserting the processor returns the identical instance
When it happens
Trigger: Inside processOutputResult, code like `return new MessageList().add(messages)` or returning a MessageList obtained from elsewhere (memory, another agent) instead of the passed-in messageList.
Common situations: Migrating processors from the old array-return style to MessageList; constructing a filtered copy of messages and returning it; wrapping the argument list in a fresh MessageList to 're-parse' it.
Related errors
- AGENT_DURABLE_METHOD_NOT_AVAILABLE
- Stream part blocked by ${processor.id}
- [Processor:${processor.id}] sendStateSignal requires Mastra
- [Processor:${processor.id}] sendStateSignal could not load t
- connect({ id, name }): "name" is required when connecting wi
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/bd24975e61bcc473.
Report an issue: GitHub.