mastra-ai/mastra · warning · Error

Concurrency limit reached, cannot resume task "${taskId}" —

Error message

Concurrency limit reached, cannot resume task "${taskId}" — retry once a slot is available

What it means

Before resuming a task, resume() checks the per-agent concurrency limit via checkConcurrency(). Unlike the normal dispatch path there is no queue or synchronous fallback for resume, so when all slots are busy the manager throws instead of silently leaving the task suspended. The caller is expected to retry once a slot frees.

Source

Thrown at packages/core/src/background-tasks/manager.ts:437

    if (this.initPromise) await this.initPromise;

    const storage = await this.getStorage();
    const task = await storage.getTask(taskId);
    if (!task) {
      throw new Error(`Task not found: ${taskId}`);
    }
    if (task.status !== 'suspended') {
      throw new Error(`Cannot resume task in status '${task.status}' (expected 'suspended')`);
    }

    const canRun = await this.checkConcurrency(task.agentId);
    if (!canRun) {
      // Resume sits outside the queue/fallback-sync paths — there's no
      // synchronous caller to fall back to, and silently leaving the task
      // suspended hides the failure from the caller. Throw and let the
      // caller retry once a slot frees.
      throw new Error(`Concurrency limit reached, cannot resume task "${taskId}" — retry once a slot is available`);
    }

    // Resume publishes directly (not via dispatch()), so it needs its own
    // lazy worker start for the library-mode process-restart case.
    await this.#ensureExecutionWorkersStarted();

    // Hand off to the worker subscriber. `task.resume` rides the same
    // `TOPIC_DISPATCH` + `WORKER_GROUP` exactly-once channel as
    // `task.dispatch`, so any worker (including a different process from
    // the one that suspended the task) can pick it up.
    await this.pubsub.publish(TOPIC_DISPATCH, {
      type: 'task.resume',
      data: { taskId, resumeData },
      runId: taskId,
    });

    return task;
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Catch the error and retry resume with backoff until a concurrency slot frees.
  2. Serialize resumes (small concurrency, e.g. p-limit) so you never exceed the agent's limit.
  3. Raise the agent's max concurrency configuration if the limit is routinely the bottleneck.

Example fix

// before
await manager.resume(taskId); // throws when slots are full
// after
async function resumeWithRetry(taskId: string, retries = 5) {
  for (let i = 0; i < retries; i++) {
    try { return await manager.resume(taskId); }
    catch (e) {
      if (!String(e).includes('Concurrency limit reached')) throw e;
      await new Promise(r => setTimeout(r, 2 ** i * 1000));
    }
  }
  throw new Error(`Could not resume ${taskId}: concurrency slots never freed`);
}
Defensive patterns

Strategy: retry

Try / catch

async function resumeWithBackoff(taskId, maxAttempts = 5) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await manager.resume(taskId);
    } catch (e) {
      if (!String(e.message).includes('Concurrency limit reached') || attempt === maxAttempts) throw e;
      await new Promise(r => setTimeout(r, 2 ** attempt * 500 + Math.random() * 250));
    }
  }
}

Prevention

When it happens

Trigger: Calling manager.resume(taskId) when the task's agent already has the maximum configured number of concurrently running tasks (agent concurrency limit reached).

Common situations: Resuming many suspended tasks in a loop after a process restart while all concurrency slots are still occupied; high-traffic deployments with tight per-agent concurrency settings.

Related errors


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