mastra-ai/mastra · error · MastraError
AGENT_MEMORY_THREAD_RESOURCE_MISMATCH
AGENT_MEMORY_THREAD_RESOURCE_MISMATCH
Error message
Thread "${thread.id}" belongs to resource "${thread.resourceId}" but resource "${resourceId}" was provided. A thread can only be used by the resource that owns it. What it means
assertThreadOwnedByResource enforces memory thread ownership: a memory thread is permanently bound to the resourceId that created it, and if a caller passes a thread whose thread.resourceId differs from the provided resourceId (and the thread has a resourceId at all), this USER error is thrown. This prevents cross-resource memory leakage — one user/tenant reading or writing another's conversation thread. It is raised from agent memory entry points (__primitive, prepareForDurableExecution, createPrepareMemoryStep).
Source
Thrown at packages/core/src/agent/memory-thread-ownership.ts:24
*
* Threads are scoped to a single resource. Without this check the agent would happily run the
* model (and tools) for a thread/resource pair that can never read or write that thread's history,
* so callers could not rely on `Agent.stream()` to reject an invalid pair before execution.
*
* Threads stored without a `resourceId` are treated as unowned so pre-existing rows keep working.
*/
export function assertThreadOwnedByResource({
thread,
resourceId,
agentName,
}: {
thread: StorageThreadType;
resourceId: string;
agentName?: string;
}): void {
if (!thread.resourceId || thread.resourceId === resourceId) return;
throw new MastraError({
id: 'AGENT_MEMORY_THREAD_RESOURCE_MISMATCH',
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
details: {
agentName: agentName ?? '',
threadId: thread.id,
expectedResourceId: thread.resourceId,
actualResourceId: resourceId,
},
text: `Thread "${thread.id}" belongs to resource "${thread.resourceId}" but resource "${resourceId}" was provided. A thread can only be used by the resource that owns it.`,
});
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Pass the resourceId that owns the thread — fetch the thread (memory.getThreadById) and use thread.resourceId, or stop passing a mismatched resourceId.
- Create a new thread for the current resourceId instead of reusing the foreign thread (e.g. omit threadId so a fresh thread is created/scoped correctly).
- If resourceIds changed scheme, migrate the stored threads (update thread.resourceId in storage) so they match the new ids.
- Audit the client for stale cached thread ids after user switch/logout and clear them.
Example fix
// before
await agent.stream(prompt, { resourceId: currentUserId, threadId: savedThreadId }); // thread owned by someone else
// after
const thread = await memory.getThreadById({ threadId: savedThreadId });
await agent.stream(prompt, { resourceId: thread.resourceId, threadId: thread.id }); Defensive patterns
Strategy: try-catch
Validate before calling
const thread = await memory.getThreadById({ threadId: requestedThreadId });
if (thread && thread.resourceId !== currentResourceId) {
// do not attempt to use it; create or fetch a thread owned by currentResourceId
throw new Error('Thread does not belong to current resource');
} Type guard
function threadOwnedBy(thread: { resourceId?: string }, resourceId: string): boolean {
return !thread.resourceId || thread.resourceId === resourceId;
} Try / catch
try {
await agent.stream(prompt, { resourceId, threadId });
} catch (e) {
if (e instanceof MastraError && e.id === 'AGENT_MEMORY_THREAD_RESOURCE_MISMATCH') {
// stale/foreign thread: clear cached threadId and start a new thread for this resource
await agent.memory.createThread({ resourceId, title: 'New conversation' });
} else throw e;
} Prevention
- Never hard-code thread ids; resolve them from the current authenticated resourceId
- Clear cached thread ids on user logout/account switch in the client
- When changing resourceId schemes, run a storage migration for existing threads
- Prefer letting Mastra create/retrieve the thread from resourceId + threadId it owns, and validate ownership before reuse
When it happens
Trigger: Calling agent.generate/stream/memory.remember with `threadId` (or thread object) belonging to userA while passing `resourceId: userB`; hard-coding a thread id in tests or code while the resource id is derived from the current session user; swapping auth backends so stored threads carry old resourceIds that no longer match newly computed ones.
Common situations: Multi-tenant apps where the logged-in user changed but the client kept the old thread id; copying thread ids between environments (staging thread used with production resource id); using a shared demo thread id in code while resource ids differ per request; renaming the resourceId scheme (e.g. 'user:1' to '1') after threads already existed.
Related errors
- Thread with id ${threadId} is for resource with id ${thread.
- Thread not found
- Thread does not belong to the active resource
- Access denied: cannot save messages for a different resource
- Access denied: thread belongs to a different resource
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a8db3126356fb1fa.
Report an issue: GitHub.