mastra-ai/mastra · error
Expected role "system" but saw ${coreMessage.role} for messa
Error message
Expected role "system" but saw ${coreMessage.role} for message ${JSON.stringify(coreMessage, null, 2)} What it means
MessageList.addSystem asserts that everything passed to it is a system message. systemMessageToAIV4Core normalized the input, and if the resulting coreMessage.role is not 'system', the call site passed a non-system message (or an unconvertible shape) to addSystem, so it throws.
Source
Thrown at packages/core/src/agent/message-list/message-list.ts:1578
| string[]
| null,
tag?: string,
) {
if (!messages) return this;
for (const message of Array.isArray(messages) ? messages : [messages]) {
this.addOneSystem(message, tag);
}
return this;
}
private addOneSystem(
message: CoreMessageV4 | AIV6Type.ModelMessage | AIV5Type.ModelMessage | MastraDBMessage | string,
tag?: string,
) {
const coreMessage = systemMessageToAIV4Core(message);
if (coreMessage.role !== `system`) {
throw new Error(
`Expected role "system" but saw ${coreMessage.role} for message ${JSON.stringify(coreMessage, null, 2)}`,
);
}
if (tag && !this.isDuplicateSystem(coreMessage, tag)) {
this.taggedSystemMessages[tag] ||= [];
this.taggedSystemMessages[tag].push(coreMessage);
if (this.isRecording) {
this.recordedEvents.push({
type: 'addSystem',
tag,
message: coreMessage,
});
}
} else if (!tag && !this.isDuplicateSystem(coreMessage)) {
this.systemMessages.push(coreMessage);
if (this.isRecording) {
this.recordedEvents.push({View on GitHub (pinned to 75dd419e61)
Solutions
- Only pass messages with role 'system' (or plain strings) to addSystem
- Route non-system messages to messageList.add() instead
- Log/inspect the JSON in the error to identify the wrongly-typed input
- Add a compile-time constraint (SystemMessage type) in your calling code
Example fix
// before
messageList.addSystem({ role: 'user', content: 'instructions' })
// after
messageList.addSystem({ role: 'system', content: 'instructions' }) Defensive patterns
Strategy: validation
Validate before calling
if (typeof msg !== 'string' && msg.role !== 'system') throw new Error('addSystem requires a system message'); Type guard
function isSystemMessage(m: any): m is { role: 'system'; content: string | any[] } { return typeof m === 'string' || m?.role === 'system'; } Try / catch
try { messageList.addSystem(msg); } catch (e) { if (e instanceof Error && e.message.includes('Expected role "system"')) { messageList.add(msg); } else throw e; } Prevention
- Only call addSystem for system-prompt content
- Type inputs as SystemMessage in calling code
- Avoid passing dynamic variables of uncertain role into addSystem
When it happens
Trigger: Calling messageList.addSystem() with a user/assistant message, a plain object missing a role, or a string-producing conversion that yields a non-system role; also passing MastraDBMessages whose role was mutated.
Common situations: Using addSystem as a generic add() by mistake; dynamic system-prompt injection passing a variable that is not always a system message; typed-erased inputs from JS callers.
Related errors
- INVALID_SYSTEM_MESSAGE_FORMAT
- INVALID_SYSTEM_MESSAGE_FORMAT
- Encountered unknown role ${role} when converting V5 ModelMes
- BUG: add handling for message role ${message.role} in messag
- INVALID_MESSAGE_CONTENT
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/0e92a96717f95102.
Report an issue: GitHub.