mastra-ai/mastra · error · Error
countCoreSystemMessageTokens: System message content must be
Error message
countCoreSystemMessageTokens: System message content must be a string
What it means
countCoreSystemMessageTokens only supports string content for system messages. Tagged or array-part system messages (content as parts array) cannot be counted by the simple role+content concatenation, so the helper throws when typeof message.content !== 'string'.
Source
Thrown at packages/core/src/processors/processors/token-limiter.ts:235
const idsToRemove = messages.filter(m => !keepIds.has(m.id)).map(m => m.id);
if (idsToRemove.length > 0) {
messageList.removeByIds(idsToRemove);
}
}
/**
* Count tokens for a system message. Accepts both untagged and tagged system messages
* read from `messageList.getAllSystemMessages()`. Only string content is supported.
*/
private async countCoreSystemMessageTokens(message: CoreMessageV4): Promise<number> {
if (message.role !== 'system') {
throw new Error(
`countCoreSystemMessageTokens can only be used with system messages, received role: ${message.role}`,
);
}
if (typeof message.content !== 'string') {
throw new Error('countCoreSystemMessageTokens: System message content must be a string');
}
const tokenString = message.role + message.content;
return this.countTokens(tokenString) + TokenLimiterProcessor.TOKENS_PER_MESSAGE;
}
/**
* Count tokens for an input message, including overhead for message structure
*/
private async countInputMessageTokens(message: MastraDBMessage): Promise<number> {
let tokenString = message.role;
let overhead = 0;
// Media is estimated rather than tokenized, so it is accumulated separately.
let mediaTokens = 0;
// Handle content based on MastraMessageV2 structure
let toolResultCount = 0; // Track tool results that will become separate messagesView on GitHub (pinned to 75dd419e61)
Solutions
- Provide system message content as a plain string: { role: 'system', content: 'instructions...' }.
- Flatten multi-part system messages into a single string of concatenated text parts before adding them.
- Upgrade @mastra/core in case a newer version counts array-content system messages.
- Pre-tokenize the structured content yourself and adjust maxTokens accordingly.
Example fix
// before
{ role: 'system', content: [{ type: 'text', text: 'You are...' }] }
// after
{ role: 'system', content: 'You are...' } Defensive patterns
Strategy: validation
Validate before calling
function assertStringSystemContent(msg) {
if (msg.role === 'system' && typeof msg.content !== 'string') {
throw new Error('System message content must be a plain string');
}
} Type guard
function hasStringSystemContent(m) {
return m.role === 'system' && typeof m.content === 'string';
} Prevention
- Always build system messages with string content.
- Flatten content-part arrays into text before assigning to system messages.
- Add a schema/lint check on message construction helpers.
When it happens
Trigger: processInputStep encounters a system message from getAllSystemMessages() whose content is an array of content parts (multi-part system message) instead of a plain string.
Common situations: Building system messages with image/tool-part content arrays; constructing CoreMessage manually with content: [{type:'text',...}]; passing structured content from a custom memory provider.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- RegexFilterProcessor requires at least one rule or preset
- TokenLimiterProcessor: System messages alone exceed token li
- TokenLimiterProcessor: No messages fit within the remaining
- ClaudeSDKAgent resumeData must include sessionId or continue
- CursorSDKAgent resumeData must include a message.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/e78e46b7df45e1c2.
Report an issue: GitHub.