mastra-ai/mastra · error

threadId must be a non-empty string or array of non-empty st

Error message

threadId must be a non-empty string or array of non-empty strings

What it means

InMemoryStorage.listMessages requires threadId to be either a non-empty string or an array of non-empty strings (after trimming whitespace). The method normalizes a single threadId into an array and then validates that the array is non-empty and every element is non-blank. This guard prevents querying messages with an undefined/empty thread set, which would otherwise be ambiguous or accidentally match nothing/all threads.

Source

Thrown at packages/core/src/storage/domains/memory/inmemory.ts:122

      }
    });
  }

  async listMessages({
    threadId,
    resourceId: optionalResourceId,
    include,
    filter,
    perPage: perPageInput,
    page = 0,
    orderBy,
  }: StorageListMessagesInput): Promise<StorageListMessagesOutput> {
    const metadataFilter = validateStorageMetadataFilter(filter?.metadata);
    // Normalize threadId to array
    const threadIds = Array.isArray(threadId) ? threadId : [threadId];

    if (threadIds.length === 0 || threadIds.some(id => !id.trim())) {
      throw new Error('threadId must be a non-empty string or array of non-empty strings');
    }

    const threadIdSet = new Set(threadIds);

    const { field, direction } = this.parseOrderBy(orderBy, 'ASC');

    // Normalize perPage for query (false → MAX_SAFE_INTEGER, 0 → 0, undefined → 40)
    const perPage = normalizePerPage(perPageInput, 40);

    if (page < 0) {
      throw new Error('page must be >= 0');
    }

    // Prevent unreasonably large page values that could cause performance issues
    const maxOffset = Number.MAX_SAFE_INTEGER / 2;
    if (page * perPage > maxOffset) {
      throw new Error('page value too large');
    }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure a valid thread exists and its id is loaded before calling listMessages.
  2. Filter out blank entries before passing an array: ids.filter(id => typeof id === 'string' && id.trim().length > 0), then skip the call if the result is empty.
  3. If threadId may legitimately be absent, guard the call site rather than passing an empty value.

Example fix

// before
await storage.listMessages({ threadId: thread?.id });
// after
if (thread?.id) {
  await storage.listMessages({ threadId: thread.id });
}
Defensive patterns

Strategy: validation

Validate before calling

function hasValidThreadIds(threadId) {
  const ids = Array.isArray(threadId) ? threadId : [threadId];
  return ids.length > 0 && ids.every(id => typeof id === 'string' && id.trim().length > 0);
}
if (!hasValidThreadIds(threadId)) return; // skip call or surface input error
await storage.listMessages({ threadId });

Type guard

function isValidThreadId(v: unknown): v is string {
  return typeof v === 'string' && v.trim().length > 0;
}
function areValidThreadIds(v: unknown): v is string[] {
  return Array.isArray(v) && v.length > 0 && v.every(isValidThreadId);
}

Prevention

When it happens

Trigger: Calling listMessages({ threadId: '' }), threadId: undefined/null (bypassing types), threadId: [], or an array containing blank entries like ['thread-1', ' '] (whitespace-only strings pass a naive check but fail trim()).

Common situations: Thread ID sourced from an uninitialized variable, a route param that is an empty string before the record loads, React effects firing before thread creation completes, or JS callers (no TypeScript) passing null/undefined.

Related errors


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