{"record":{"id":"fcb295fb34f09dde","repo":"mastra-ai/mastra","slug":"thread-not-found-threadid-fcb295","errorCode":null,"errorMessage":"Thread not found: ${threadId}","messagePattern":"Thread not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agent-controller/session.ts","lineNumber":445,"sourceCode":"    threadId,\n    expectedResourceId,\n    expectedProjectPath,\n  }: {\n    threadId: string;\n    expectedResourceId: string;\n    expectedProjectPath: string;\n  }): Promise<AgentControllerThread> {\n    if (!this.#store?.hasStorage()) {\n      throw new Error('Memory is not configured on this AgentController');\n    }\n    const thread = await this.#store.getById({ threadId });\n    if (\n      !thread ||\n      thread.resourceId !== expectedResourceId ||\n      thread.metadata?.projectPath !== expectedProjectPath ||\n      expectedResourceId === this.#getResourceId()\n    ) {\n      throw new Error(`Thread not found: ${threadId}`);\n    }\n    return this.#cloneThread({\n      sourceThreadId: thread.id,\n      resourceId: this.#getResourceId(),\n      title: thread.title,\n      metadata: thread.metadata,\n    });\n  }\n\n  /**\n   * Load a thread and verify it belongs to this session's resourceId before\n   * allowing access. Threads owned by another resource are treated as missing\n   * so a session can never read, switch to, rename, or delete a thread it does\n   * not own (the thread id is otherwise an unguessable but unscoped key). Throws\n   * `Thread not found: <id>` when the thread is absent or owned by someone else.\n   */\n  async #requireOwnedThread({ threadId }: { threadId: string }): Promise<AgentControllerThread> {\n    const thread = await this.#store?.getById({ threadId });","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent-controller/session.ts#L427-L463","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify the threadId exists in storage and matches the expected resourceId and metadata.projectPath before calling.","Call listThreads/lookup first and skip cloning when the thread is already owned by the current resource.","Handle the throw as 'not cloneable for this resource' in UI and offer thread creation instead."],"exampleFix":"// before\nawait session.cloneToCurrentResource({ threadId: someId, expectedResourceId: 'a', expectedProjectPath: '/p' });\n// after\nconst thread = await store.getById({ threadId: someId });\nif (thread && thread.resourceId === 'a' && thread.metadata?.projectPath === '/p') {\n  await session.cloneToCurrentResource({ threadId: someId, expectedResourceId: 'a', expectedProjectPath: '/p' });\n}","handlingStrategy":"validation","validationCode":"const thread = await store.getById({ threadId });\nconst cloneable = !!thread && thread.resourceId === expectedResourceId && thread.metadata?.projectPath === expectedProjectPath;\nif (!cloneable) throw new Error('Thread is not cloneable into this resource');","typeGuard":null,"tryCatchPattern":"try {\n  await session.cloneToCurrentResource(args);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Thread not found: ')) {\n    // treat as not-found/not-permitted; refresh thread list\n  } else throw e;\n}","preventionTips":["Only pass threadIds obtained from listing the same resource's threads","Skip clone when the thread is already the current one","Keep metadata.projectPath in sync with the actual project","Refresh UI thread lists after deletions"],"tags":["state","ownership","threading"],"backgroundTag":"thread-not-found","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}