mastra-ai/mastra · error

Thread with id ${threadId} is for resource with id ${thread.

Error message

Thread with id ${threadId} is for resource with id ${thread.resourceId} but resource ${resourceId} was queried.

What it means

When recalling, if the thread exists but belongs to a different resource than the resourceId passed in, this error is thrown to prevent cross-tenant data leakage. The message shows both the thread's actual resourceId and the queried one.

Source

Thrown at packages/memory/src/index.ts:583

    return memoryStore.listMessagesByResourceId(args);
  }

  protected async validateThreadIsOwnedByResource(threadId: string, resourceId: string, config: MemoryConfigInternal) {
    const resourceScope =
      (typeof config?.semanticRecall === 'object' && config?.semanticRecall?.scope !== `thread`) ||
      config.semanticRecall === true;

    const thread = await this.getThreadById({ threadId });

    // For resource-scoped semantic recall, we don't need to validate that the specific thread exists
    // because we're searching across all threads for the resource
    if (!thread && !resourceScope) {
      throw new Error(`No thread found with id ${threadId}`);
    }

    // If thread exists, validate it belongs to the correct resource
    if (thread && thread.resourceId !== resourceId) {
      throw new Error(
        `Thread with id ${threadId} is for resource with id ${thread.resourceId} but resource ${resourceId} was queried.`,
      );
    }
  }

  private createMemorySpan(
    operationType: MemoryOperationAttributes['operationType'],
    observabilityContext?: Partial<ObservabilityContext>,
    input?: any,
    attributes?: Partial<MemoryOperationAttributes>,
  ) {
    const currentSpan = observabilityContext?.tracingContext?.currentSpan;
    if (!currentSpan) return undefined;
    return currentSpan.createChildSpan({
      type: SpanType.MEMORY_OPERATION,
      name: `memory: ${operationType}`,
      entityType: EntityType.MEMORY,
      entityName: 'Memory',

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Verify the thread belongs to the resource before recall, or derive resourceId from the fetched thread
  2. Do not share thread ids across resources; scope thread creation/lookup per user
  3. Check for swapped arguments (threadId vs resourceId) in the call site

Example fix

// before
await memory.recall({ threadId, resourceId: currentUserId });
// after
const thread = await memory.getThreadById({ threadId });
if (thread && thread.resourceId !== currentUserId) {
  throw new Error('Thread does not belong to this user');
}
await memory.recall({ threadId, resourceId: currentUserId });
Defensive patterns

Strategy: validation

Validate before calling

const thread = await memory.getThreadById({ threadId });
if (thread && thread.resourceId !== resourceId) throw new Error('Thread/resource mismatch');

Try / catch

try {
  await memory.recall({ threadId, resourceId });
} catch (e) {
  if (e instanceof Error && e.message.includes('is for resource with id')) {
    // handle cross-tenant access attempt / wrong id pairing
  } else throw e;
}

Prevention

When it happens

Trigger: memory.recall({ threadId, resourceId }) where thread.resourceId !== resourceId — e.g. passing user A's resourceId with user B's thread id, or ids swapped in the call.

Common situations: Multi-tenant apps where thread ids are cached per session but resource ids change (user re-login, shared threads); mixing up argument order; copying a threadId between environments.

Related errors


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