mastra-ai/mastra · error · HTTPException
Access denied: workflow run belongs to a different resource
Error message
Access denied: workflow run belongs to a different resource
What it means
validateRunOwnership throws this 403 when a workflow run's stored resourceId differs from the request's effective resource ID. It stops users from resuming, recovering, viewing, or deleting workflow runs that belong to a different resource, mirroring thread ownership checks for workflows.
Source
Thrown at packages/server/src/server/handlers/utils.ts:175
mastra,
user: user as { id: string; [key: string]: unknown },
threadId,
resourceId: thread?.resourceId ?? effectiveResourceId,
requestContext,
permission,
});
}
/**
* Validates that a workflow run belongs to the specified resourceId.
* Throws 403 if the run exists but belongs to a different resource.
*/
export async function validateRunOwnership(
run: { resourceId?: string | null } | null | undefined,
effectiveResourceId: string | undefined,
): Promise<void> {
if (run && effectiveResourceId && run.resourceId && run.resourceId !== effectiveResourceId) {
throw new HTTPException(403, { message: 'Access denied: workflow run belongs to a different resource' });
}
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Resume/recover the run with the same resource ID (memory.resource or authenticated user) under which it was started.
- Look up the run's resourceId in storage and align the request's effective resource ID accordingly.
- Start a new workflow run under the current resource if cross-resource access was unintentional.
- Ensure mapUserToResourceId is stable across environments so the same user maps to the same resource ID.
Example fix
// before
await fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-2' }, ... }) }); // run started as user-1
// after
await fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-1' }, ... }) }); Defensive patterns
Strategy: try-catch
Validate before calling
async function assertRunOwnable(runId: string, resourceId: string, fetchRun: (id: string) => Promise<{ resourceId?: string | null } | null>) {
const run = await fetchRun(runId);
if (run?.resourceId && run.resourceId !== resourceId) {
throw new Error(`Run ${runId} belongs to resource '${run.resourceId}', not '${resourceId}'`);
}
} Type guard
function runBelongsToResource(run: { resourceId?: string | null } | null, resourceId: string): boolean {
return !!run && (!run.resourceId || run.resourceId === resourceId);
} Try / catch
try {
const res = await fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify(payload) });
if (res.status === 403) throw new Error(`Run ${runId} belongs to a different resource; resume with the originating resource ID`);
return await res.json();
} catch (e) { throw e; } Prevention
- Store the resourceId alongside runId when you start workflow runs and reuse it for resume/recover.
- Do not share runIds across tenants, users, or environments.
- Keep mapUserToResourceId stable so the same identity maps to the same resource ID everywhere.
- Log resourceId with runId to make ownership mismatches easy to diagnose.
When it happens
Trigger: Calling RESUME_STREAM, RECOVER, resume-stream-until-idle, GET/DELETE workflow-run-by-id, or workflow resume-stream routes with a runId whose resourceId does not match the caller's effectiveResourceId (from body memory.resource or authenticated user).
Common situations: Sharing runIds across users/environments; resuming a run started under a different memory.resource or different mapped auth user; CI seeding runs with one resource ID while tests run as another; copying runIds from logs of another tenant.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access denied: durable run belongs to a different resource
- Access denied: cannot save messages for a different resource
- Access denied: unable to verify message ownership
- You do not have permission to disconnect this connection
- You do not have permission to view usage for this connection
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/dcfdd73ac356681b.
Report an issue: GitHub.