mastra-ai/mastra · error · HTTPException
Access denied: durable run belongs to a different resource
Error message
Access denied: durable run belongs to a different resource
What it means
Thrown by validateDurableToolCallAccess (used by the approve/decline tool-call routes, both generate and stream variants) when no durable workflow run exists for the given runId under the AGENTIC_LOOP workflow. The 403 intentionally hides whether the run exists, tying the run to the caller's resource scope.
Source
Thrown at packages/server/src/server/handlers/agents.ts:217
runId,
toolCallId,
requestContext,
}: {
mastra: any;
agent: Agent;
runId: string;
toolCallId: string;
requestContext: RequestContext;
}): Promise<void> {
if (!isDurableAgentLike(agent)) return;
const workflowsStore = await mastra.getStorage()?.getStore('workflows');
const workflowRun = await workflowsStore?.getWorkflowRunById({
workflowName: DurableStepIds.AGENTIC_LOOP,
runId,
});
if (!workflowRun) {
throw new HTTPException(403, { message: 'Access denied: durable run belongs to a different resource' });
}
let snapshot = workflowRun.snapshot as Record<string, any> | string | undefined;
if (typeof snapshot === 'string') {
try {
snapshot = JSON.parse(snapshot) as Record<string, any>;
} catch {
snapshot = undefined;
}
}
const input = snapshot?.context?.input;
const persistedResourceIds = new Set(
[
workflowRun.resourceId,
input?.state?.resourceId,
input?.messageListState?.memoryInfo?.resourceId,
input?.requestContextEntries?.[MASTRA_RESOURCE_ID_KEY],View on GitHub (pinned to 75dd419e61)
Solutions
- Confirm the runId comes from a currently-suspended durable agent run in the same storage backend/environment
- Re-check that the caller's resourceId matches the resource that started the run — approve/decline from the originating resource's context
- Handle the 403 by refreshing run state from the agent's tool-call list instead of retrying the stale runId
Example fix
// before
await client.approveToolCall({ runId: staleRunId, toolCallId })
// after
const pending = await client.getPendingToolCalls({ resourceId });
if (pending.some(c => c.runId === runId && c.toolCallId === toolCallId)) {
await client.approveToolCall({ runId, toolCallId });
} Defensive patterns
Strategy: try-catch
Validate before calling
// client-side: only act on runIds returned by the durable agent run in this environment
const pending = await getPendingToolCalls({ resourceId: myResourceId });
if (!pending.some(c => c.runId === runId)) return skip(); Type guard
function isRunnableRun(run: { status: string } | null | undefined): run is { status: 'suspended' } {
return !!run && run.status === 'suspended';
} Try / catch
try {
await client.approveToolCall({ runId, toolCallId });
} catch (e) {
if (isHttpException(e, 403) && String(e.message).includes('durable run')) {
// run unknown to this storage/resource: refresh pending list, do not retry
}
} Prevention
- Only use runIds obtained from the same server/storage instance
- Treat approve/decline as one-shot actions; disable controls after the first call
- In multi-tenant apps, never accept runIds from cross-user input without re-authorization
- Poll run status before acting to confirm the run is still suspended
When it happens
Trigger: POSTing to the durable tool approve/decline endpoints with a runId that was never persisted, was already completed and pruned, or belongs to another resource; storage not returning the run because getWorkflowRunById is scoped by workflowName DurableStepIds.AGENTIC_LOOP and the run was created under a different workflow.
Common situations: Approving a tool call after the run finished and its snapshot was cleaned up; sharing a runId between users/resources (multi-tenant setups); pointing at the wrong Mastra server/environment where the runId does not exist; calling approve twice after the run transitioned out of suspension.
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: 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
- Access denied: thread belongs to a different resource
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/7d5c879d0fb7675c.
Report an issue: GitHub.