{"record":{"id":"1984148ee7f766cf","repo":"mastra-ai/mastra","slug":"thread-not-found-overrides-threadid","errorCode":null,"errorMessage":"Thread not found: ${overrides.threadId}","messagePattern":"Thread not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agent-controller/agent-controller.ts","lineNumber":696,"sourceCode":"      new Session({\n        resourceId: effectiveResourceId,\n        id,\n        ownerId,\n        tags,\n        state: {\n          initialState,\n          stateSchema: this.config.stateSchema,\n        },\n        workspace: workspaceToConnect,\n        browser: browserToConnect,\n      }),\n    );\n\n    if (overrides?.threadId) {\n      const existingThread = await session.thread.getById({ threadId: overrides.threadId });\n      if (existingThread) {\n        if (existingThread.resourceId !== effectiveResourceId) {\n          throw new Error(`Thread not found: ${overrides.threadId}`);\n        }\n        await this.config.threadLock?.acquire(existingThread.id);\n        session.thread.set({ threadId: existingThread.id });\n        await session.thread.loadMetadata();\n        await session.thread.ensureCurrentSubscription();\n      } else {\n        await session.thread.create({ id: overrides.threadId });\n      }\n    } else {\n      // Same scope `thread.create()` stamps, matched strictly: a thread outside\n      // this session's scope — including one carrying no scope at all — belongs\n      // to nobody here and must not be auto-resumed.\n      const scopeEntries = Object.entries(session.getThreadScope());\n\n      const threads = await session.thread.list();\n      const candidates = threads.filter(t => {\n        const metadata = (t.metadata as Record<string, unknown> | undefined) ?? {};\n        return scopeEntries.every(([key, value]) => metadata[key] === value);","sourceCodeStart":678,"sourceCodeEnd":714,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent-controller/agent-controller.ts#L678-L714","documentation":"createSessionForResource was asked to open a thread by overrides.threadId, the thread exists in storage, but its resourceId does not match the effective resource id of the caller. Since a thread is scoped to a single resource, a thread belonging to another resource is treated as 'not found' rather than leaked across resource boundaries. This protects multi-tenant isolation of conversations.","triggerScenarios":"Calling an API that accepts overrides.threadId (e.g. session creation / resume) with a threadId whose persisted resourceId differs from the effectiveResourceId computed from the request (config.resourceId / request context). Also occurs when resourceId is recomputed differently (e.g. changed casing/format) between the thread's creation and the lookup.","commonSituations":"Multi-user apps passing one user's threadId while authenticating as another user; changing resourceId derivation logic after threads were created; storing threadIds client-side and reusing them after a resourceId config change.","solutions":["Pass the correct resourceId that matches the thread's stored resourceId, or omit overrides.threadId to create a fresh thread","Verify how effectiveResourceId is derived and ensure it is identical to the one used when the thread was created (stable derivation, no case/format drift)","If the thread genuinely belongs to another resource, look it up under that resource instead of forcing a cross-resource access","Check storage for the thread's actual resourceId (e.g. via memory storage getThreadById) to confirm the mismatch"],"exampleFix":"// before\nawait controller.createSessionForResource({ overrides: { threadId: threadIdFromOtherUser } });\n// after\nconst thread = await memoryStorage.getThreadById({ threadId: threadIdFromOtherUser });\nif (thread?.resourceId === currentResourceId) {\n  await controller.createSessionForResource({ overrides: { threadId: threadIdFromOtherUser } });\n} else {\n  await controller.createSessionForResource({}); // fresh thread\n}","handlingStrategy":"validation","validationCode":"const thread = await memoryStorage.getThreadById({ threadId });\nif (thread && thread.resourceId !== currentResourceId) throw new Error('thread belongs to another resource');","typeGuard":null,"tryCatchPattern":"try {\n  await controller.createSessionForResource({ overrides: { threadId } });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Thread not found:')) {\n    // fall back to a fresh thread\n    return controller.createSessionForResource({});\n  }\n  throw e;\n}","preventionTips":["Derive resourceId with a stable, deterministic function shared by create and lookup paths","Never accept raw threadIds from client input without verifying ownership against the authenticated resource","Store resourceId alongside threadId in client-side caches and revalidate before reuse"],"tags":["multi-tenancy","resource-isolation","thread"],"backgroundTag":"resource-id-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}