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
- Verify the thread belongs to the resource before recall, or derive resourceId from the fetched thread
- Do not share thread ids across resources; scope thread creation/lookup per user
- 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
- Never accept threadId/resourceId pairs from untrusted input without checking ownership
- Scope thread id caching per user/session
- Watch for swapped argument order in recall calls
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
- AGENT_MEMORY_THREAD_RESOURCE_MISMATCH
- 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/a28155be97989e13.
Report an issue: GitHub.