mastra-ai/mastra · error · MastraError

WORKFLOW_RESUME_ALREADY_CLAIMED

WORKFLOW_RESUME_ALREADY_CLAIMED

Error message

This suspended workflow run was already resumed by another caller. Workflow "${this.workflowId}" run "${this.runId}" moved from "${snapshot.status}" to "${current.status}" before this resume could claim it. Only one resume() call may continue a given suspension; re-read the run state before resuming again.

What it means

A MastraError (id WORKFLOW_RESUME_ALREADY_CLAIMED, USER category) thrown when two callers try to resume the same suspended run concurrently. The store's compare-and-set (status 'suspended' -> 'running') failed, meaning another resume already claimed the run; the error reports the status transition it observed. Only one resume() may continue a given suspension.

Source

Thrown at packages/core/src/workflows/workflow.ts:4369

      opts: { status: 'running', expectedStatus: 'suspended' },
    });

    if (claimed) {
      return;
    }

    // The compare-and-set found a status other than `suspended`. Re-read so the error names the
    // status the run actually landed in rather than guessing.
    const current = await workflowsStore.loadWorkflowSnapshot({
      workflowName: this.workflowId,
      runId: this.runId,
    });

    if (!current) {
      throw new Error('No snapshot found for this workflow run: ' + this.workflowId + ' ' + this.runId);
    }

    throw new MastraError({
      id: 'WORKFLOW_RESUME_ALREADY_CLAIMED',
      domain: ErrorDomain.MASTRA_WORKFLOW,
      category: ErrorCategory.USER,
      text:
        `This suspended workflow run was already resumed by another caller. Workflow "${this.workflowId}" run "${this.runId}" ` +
        `moved from "${snapshot.status}" to "${current.status}" before this resume could claim it. ` +
        `Only one resume() call may continue a given suspension; re-read the run state before resuming again.`,
      details: {
        workflowId: this.workflowId,
        runId: this.runId,
        expectedStatus: 'suspended',
        actualStatus: current.status ?? 'unknown',
      },
    });
  }

  protected async _resume<TResume>(
    params: {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Treat as expected contention: catch it, re-load the run state, and skip if status is 'running'/'completed'.
  2. Make resume idempotent on your side — key the trigger (webhook/job) by runId so duplicates are dropped.
  3. Check the run's current status via getWorkflowRunState() before issuing another resume.
  4. If using a store without concurrent-update support, note resumes cannot be de-duplicated atomically and guard at the application level (lock/queue).

Example fix

// before
await run.resume({ resumeData });
// after
try {
  await run.resume({ resumeData });
} catch (e) {
  if ((e as any).id === 'WORKFLOW_RESUME_ALREADY_CLAIMED') {
    const state = await run.getWorkflowRunState(); // another caller resumed; re-read and no-op
    return;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const state = await run.getWorkflowRunState();
if (state.status !== 'suspended') return; // someone else already advanced it

Type guard

function isAlreadyClaimed(e: unknown): e is { id: 'WORKFLOW_RESUME_ALREADY_CLAIMED' } {
  return typeof e === 'object' && e !== null && (e as any).id === 'WORKFLOW_RESUME_ALREADY_CLAIMED';
}

Try / catch

try {
  await run.resume({ resumeData, step });
} catch (e) {
  if (isAlreadyClaimed(e)) {
    logger.info('Resume already claimed by another caller; skipping');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Two resume() calls (e.g. from two server instances, a webhook retry plus a poller, or double-clicked UI) racing on the same runId while the storage adapter supports concurrent updates (updateWorkflowState CAS).

Common situations: At-least-once webhook deliveries retried resume calls; horizontally scaled deployments sharing one database; frontend firing resume twice; job scheduler retries after a timeout that actually succeeded.

Related errors


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