mastra-ai/mastra · error · HTTPException
Access denied: unable to verify message thread access
Error message
Access denied: unable to verify message thread access
What it means
Thrown by `enforceDeleteMessagesThreadAccess` when at least one of the requested message ids either does not exist or exists but has no `threadId`, so thread-level access cannot be verified. The library refuses to delete messages whose owning thread it cannot determine, treating it as access-denied (403) rather than deleting blindly.
Source
Thrown at packages/server/src/server/handlers/memory.ts:169
async function enforceDeleteMessagesThreadAccess({
mastra,
requestContext,
memoryStore,
messageIds,
effectiveResourceId,
}: {
mastra: any;
requestContext?: RequestContext;
memoryStore: MemoryStorage;
messageIds: string[];
effectiveResourceId?: string;
}): Promise<void> {
const { messages } = await memoryStore.listMessagesById({ messageIds });
const threadIds = [...new Set(messages.map(m => m.threadId).filter(Boolean))] as string[];
if (messages.some(message => !message.threadId)) {
throw new HTTPException(403, { message: 'Access denied: unable to verify message thread access' });
}
for (const threadId of threadIds) {
const thread = await memoryStore.getThreadById({ threadId });
if (!thread) {
throw new HTTPException(403, { message: 'Access denied: unable to verify message thread access' });
}
await enforceThreadAccess({
mastra,
requestContext,
threadId,
thread,
effectiveResourceId,
permission: MastraFGAPermissions.MEMORY_DELETE,
});
}
}View on GitHub (pinned to 75dd419e61)
Solutions
- Re-fetch the thread's current messages and delete only ids that still exist in the list.
- Filter out messages with no `threadId` before sending the delete request, and handle detached messages via a separate flow.
- Verify you are using the same resourceId/storage scope the messages were created under.
- Make the delete idempotent client-side: tolerate 403 for already-deleted ids and retry only the remaining ids.
Example fix
// before
await api.deleteMessages({ messageIds: allIds })
// after
const current = await api.getMessages(threadId);
const deletable = allIds.filter(id => current.messages.some(m => m.id === id && m.threadId));
await api.deleteMessages({ messageIds: deletable }); Defensive patterns
Strategy: validation
Validate before calling
const { messages } = await api.listThreadMessages(threadId);
const deletable = requestedIds.filter(id => messages.some(m => m.id === id && m.threadId));
if (deletable.length !== requestedIds.length) {
console.warn('Skipping ids not found or without thread:', requestedIds.filter(id => !deletable.includes(id)));
} Type guard
function isDeletable(m: { id: string; threadId?: string | null }): m is { id: string; threadId: string } {
return typeof m.threadId === 'string' && m.threadId.length > 0;
} Try / catch
try {
await api.deleteMessages({ messageIds });
} catch (e) {
if (isHttpError(e, 403) && e.message.includes('thread access')) {
// refetch current messages and retry with only valid ids
}
throw e;
} Prevention
- Always scope message ids from a fresh fetch of the thread before deleting.
- Treat delete as idempotent and handle 403 on already-deleted ids.
- Never delete ids captured in long-lived client state without revalidation.
When it happens
Trigger: DELETE /api/memory/messages (DELETE_MESSAGES_ROUTE) with a body of `messageIds` where one or more ids are stale/already deleted, belong to another tenant, or were stored without a threadId (e.g. detached/unthreaded messages).
Common situations: See trigger scenarios.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access denied: cannot save messages for a different resource
- Access denied: thread belongs to a different resource
- You need the admin role in this organization to manage datab
- AGENT_MEMORY_THREAD_RESOURCE_MISMATCH
- Thread with id ${threadId} is for resource with id ${thread.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/5472b86bb1b67f24.
Report an issue: GitHub.