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 messages

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Provide system message content as a plain string: { role: 'system', content: 'instructions...' }.
  2. Flatten multi-part system messages into a single string of concatenated text parts before adding them.
  3. Upgrade @mastra/core in case a newer version counts array-content system messages.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/e78e46b7df45e1c2. Report an issue: GitHub.