mastra-ai/mastra · error

Thread not found: ${threadId}

Error message

Thread not found: ${threadId}

What it means

After loading the thread from storage, cloneToCurrentResource validates it exists, belongs to expectedResourceId, carries the expected projectPath metadata, and is NOT already owned by the current resource. If any check fails the library reports 'Thread not found' rather than leaking the real reason (wrong owner, foreign project, or already-current), keeping thread ids unscoped.

Source

Thrown at packages/core/src/agent-controller/session.ts:445

    threadId,
    expectedResourceId,
    expectedProjectPath,
  }: {
    threadId: string;
    expectedResourceId: string;
    expectedProjectPath: string;
  }): Promise<AgentControllerThread> {
    if (!this.#store?.hasStorage()) {
      throw new Error('Memory is not configured on this AgentController');
    }
    const thread = await this.#store.getById({ threadId });
    if (
      !thread ||
      thread.resourceId !== expectedResourceId ||
      thread.metadata?.projectPath !== expectedProjectPath ||
      expectedResourceId === this.#getResourceId()
    ) {
      throw new Error(`Thread not found: ${threadId}`);
    }
    return this.#cloneThread({
      sourceThreadId: thread.id,
      resourceId: this.#getResourceId(),
      title: thread.title,
      metadata: thread.metadata,
    });
  }

  /**
   * Load a thread and verify it belongs to this session's resourceId before
   * allowing access. Threads owned by another resource are treated as missing
   * so a session can never read, switch to, rename, or delete a thread it does
   * not own (the thread id is otherwise an unguessable but unscoped key). Throws
   * `Thread not found: <id>` when the thread is absent or owned by someone else.
   */
  async #requireOwnedThread({ threadId }: { threadId: string }): Promise<AgentControllerThread> {
    const thread = await this.#store?.getById({ threadId });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Verify the threadId exists in storage and matches the expected resourceId and metadata.projectPath before calling.
  2. Call listThreads/lookup first and skip cloning when the thread is already owned by the current resource.
  3. Handle the throw as 'not cloneable for this resource' in UI and offer thread creation instead.

Example fix

// before
await session.cloneToCurrentResource({ threadId: someId, expectedResourceId: 'a', expectedProjectPath: '/p' });
// after
const thread = await store.getById({ threadId: someId });
if (thread && thread.resourceId === 'a' && thread.metadata?.projectPath === '/p') {
  await session.cloneToCurrentResource({ threadId: someId, expectedResourceId: 'a', expectedProjectPath: '/p' });
}
Defensive patterns

Strategy: validation

Validate before calling

const thread = await store.getById({ threadId });
const cloneable = !!thread && thread.resourceId === expectedResourceId && thread.metadata?.projectPath === expectedProjectPath;
if (!cloneable) throw new Error('Thread is not cloneable into this resource');

Try / catch

try {
  await session.cloneToCurrentResource(args);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Thread not found: ')) {
    // treat as not-found/not-permitted; refresh thread list
  } else throw e;
}

Prevention

When it happens

Trigger: Calling cloneToCurrentResource with a threadId that was deleted, belongs to a different resourceId, lacks metadata.projectPath, or is already owned by the current resource (expectedResourceId === current resource).

Common situations: Stale UI references to a deleted thread; copying a thread-id from another user/project; re-invoking clone on the thread already active in this session.

Related errors


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