{"record":{"id":"dcfdd73ac356681b","repo":"mastra-ai/mastra","slug":"access-denied-workflow-run-belongs-to-a-different","errorCode":null,"errorMessage":"Access denied: workflow run belongs to a different resource","messagePattern":"Access denied: workflow run belongs to a different resource","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"packages/server/src/server/handlers/utils.ts","lineNumber":175,"sourceCode":"    mastra,\n    user: user as { id: string; [key: string]: unknown },\n    threadId,\n    resourceId: thread?.resourceId ?? effectiveResourceId,\n    requestContext,\n    permission,\n  });\n}\n\n/**\n * Validates that a workflow run belongs to the specified resourceId.\n * Throws 403 if the run exists but belongs to a different resource.\n */\nexport async function validateRunOwnership(\n  run: { resourceId?: string | null } | null | undefined,\n  effectiveResourceId: string | undefined,\n): Promise<void> {\n  if (run && effectiveResourceId && run.resourceId && run.resourceId !== effectiveResourceId) {\n    throw new HTTPException(403, { message: 'Access denied: workflow run belongs to a different resource' });\n  }\n}\n","sourceCodeStart":157,"sourceCodeEnd":178,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/utils.ts#L157-L178","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nawait fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-2' }, ... }) }); // run started as user-1\n// after\nawait fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify({ memory: { resource: 'user-1' }, ... }) });","handlingStrategy":"try-catch","validationCode":"async function assertRunOwnable(runId: string, resourceId: string, fetchRun: (id: string) => Promise<{ resourceId?: string | null } | null>) {\n  const run = await fetchRun(runId);\n  if (run?.resourceId && run.resourceId !== resourceId) {\n    throw new Error(`Run ${runId} belongs to resource '${run.resourceId}', not '${resourceId}'`);\n  }\n}","typeGuard":"function runBelongsToResource(run: { resourceId?: string | null } | null, resourceId: string): boolean {\n  return !!run && (!run.resourceId || run.resourceId === resourceId);\n}","tryCatchPattern":"try {\n  const res = await fetch(`/api/workflows/myWorkflow/runs/${runId}/resume`, { method: 'POST', body: JSON.stringify(payload) });\n  if (res.status === 403) throw new Error(`Run ${runId} belongs to a different resource; resume with the originating resource ID`);\n  return await res.json();\n} catch (e) { throw e; }","preventionTips":["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."],"tags":["http-403","authorization","workflows","multi-tenancy"],"backgroundTag":"resource-ownership-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}