mastra-ai/mastra · error · HTTPException

Access denied: thread belongs to a different resource

Error message

Access denied: thread belongs to a different resource

What it means

validateThreadOwnership throws this 403 when a thread's resourceId does not match the effective resource ID derived from the request (body memory.resource or authenticated user). It prevents a user from reading, signaling, subscribing to, or aborting another user's conversation thread.

Source

Thrown at packages/server/src/server/handlers/utils.ts:121

export function getEffectiveThreadId(
  requestContext: RequestContext | undefined,
  clientThreadId: string | undefined,
): string | undefined {
  const contextThreadId = requestContext?.get(MASTRA_THREAD_ID_KEY) as string | undefined;
  return contextThreadId || clientThreadId;
}

/**
 * Validates that a thread belongs to the specified resourceId.
 * Throws 403 if the thread exists but belongs to a different resource.
 * Threads with no resourceId are accessible to all (shared threads).
 */
export async function validateThreadOwnership(
  thread: { resourceId?: string | null } | null | undefined,
  effectiveResourceId: string | undefined,
): Promise<void> {
  if (thread && effectiveResourceId && thread.resourceId && thread.resourceId !== effectiveResourceId) {
    throw new HTTPException(403, { message: 'Access denied: thread belongs to a different resource' });
  }
}

/**
 * Validates both coarse resource ownership and fine-grained thread access.
 * FGA enforcement is a no-op when no FGA provider is configured.
 */
export async function enforceThreadAccess({
  mastra,
  requestContext,
  threadId,
  thread,
  effectiveResourceId,
  permission = MastraFGAPermissions.MEMORY_READ,
}: {
  mastra: any;
  requestContext?: RequestContext;
  threadId: string;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use the same memory.resource (or authenticated identity) that originally created the thread.
  2. Create a new thread scoped to the current resource instead of reusing a foreign threadId.
  3. Check storage for the thread's resourceId and align your requests with it.
  4. Fix mapUserToResourceId or body memory.resource inconsistencies across environments.

Example fix

// before
const res = await fetch(`/api/agents/assistant/thread/${threadId}/signal`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-b' }, ... }) }); // thread belongs to user-a
// after
const res = await fetch(`/api/agents/assistant/thread/${threadId}/signal`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-a' }, ... }) }); // or use a thread created for user-b
Defensive patterns

Strategy: try-catch

Validate before calling

async function assertThreadOwnable(threadId: string, resourceId: string, fetchThread: (id: string) => Promise<{ resourceId?: string | null } | null>) {
  const thread = await fetchThread(threadId);
  if (thread?.resourceId && thread.resourceId !== resourceId) {
    throw new Error(`Thread ${threadId} belongs to resource '${thread.resourceId}', not '${resourceId}'`);
  }
}

Type guard

function threadBelongsToResource(thread: { resourceId?: string | null } | null, resourceId: string): boolean {
  return !!thread && (!thread.resourceId || thread.resourceId === resourceId);
}

Try / catch

try {
  const res = await fetch(`/api/agents/assistant/thread/${threadId}/signal`, { method: 'POST', body: JSON.stringify(payload) });
  if (res.status === 403) throw new Error(`Thread ${threadId} is owned by a different resource; use a thread created for this resource`);
  return await res.json();
} catch (e) { throw e; }

Prevention

When it happens

Trigger: Calling SEND_AGENT_SIGNAL, agent message, abort-thread, subscribe-thread, or stream-until-idle routes with a threadId whose stored resourceId differs from the caller's effectiveResourceId.

Common situations: Reusing a threadId from another user/test account; switching memory.resource between requests on the same thread; multiple devs sharing a dev server with different resource IDs; seeded threads with stale resourceId values after changing the resource mapping.

Understand the failure class

Related errors


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