ruvnet/ruflo · error

Task ${taskId} has exceeded max retries

Error message

Task ${taskId} has exceeded max retries

What it means

retryTask() reads retryCount and maxRetries from task.metadata (maxRetries defaults to 3) and refuses to reset a task whose retryCount has already reached the ceiling. The task stays in its failed state, forcing the caller to either create a fresh task or raise the budget. This prevents infinite retry loops on permanently failing tasks.

Source

Thrown at v3/@claude-flow/shared/src/core/orchestrator/task-manager.ts:248

    this.metrics.cancelledTasks++;

    this.eventBus.emit(SystemEventTypes.TASK_CANCELLED, {
      taskId,
      reason: reason ?? 'User requested',
    });
  }

  async retryTask(taskId: string): Promise<void> {
    const task = this.tasks.get(taskId);
    if (!task) {
      throw new Error(`Task not found: ${taskId}`);
    }

    const retryCount = (task.metadata?.retryCount as number) ?? 0;
    const maxRetries = (task.metadata?.maxRetries as number) ?? 3;

    if (retryCount >= maxRetries) {
      throw new Error(`Task ${taskId} has exceeded max retries`);
    }

    task.status = 'pending';
    task.assignedAgent = undefined;
    task.startedAt = undefined;
    task.completedAt = undefined;
    task.error = undefined;
    task.metadata = {
      ...task.metadata,
      retryCount: retryCount + 1,
    };

    await this.queue.enqueue(task);

    this.eventBus.emit(SystemEventTypes.TASK_RETRY, {
      taskId,
      attempt: retryCount + 1,
      maxAttempts: maxRetries,

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Create a replacement task via createTask() with fresh metadata (retryCount: 0) instead of retrying the exhausted one
  2. If failures are genuinely transient, set a higher metadata.maxRetries when creating the task
  3. Before retrying, compare taskManager.getTask(taskId).metadata.retryCount against maxRetries

Example fix

// before
await taskManager.retryTask(taskId); // throws: exceeded max retries

// after
const task = taskManager.getTask(taskId)!;
const { retryCount = 0, maxRetries = 3 } = task.metadata ?? {};
if (retryCount < maxRetries) {
  await taskManager.retryTask(taskId);
} else {
  await taskManager.createTask({ ...originalParams, metadata: { ...task.metadata, retryCount: 0 } });
}
Defensive patterns

Strategy: validation

Validate before calling

const task = taskManager.getTask(taskId);
const retryCount = (task?.metadata?.retryCount as number) ?? 0;
const maxRetries = (task?.metadata?.maxRetries as number) ?? 3;
if (retryCount < maxRetries) {
  await taskManager.retryTask(taskId);
} else {
  await taskManager.createTask({ ...originalParams, metadata: { retryCount: 0, maxRetries } });
}

Type guard

function canRetry(task: ITask | undefined): boolean {
  const retryCount = (task?.metadata?.retryCount as number) ?? 0;
  const maxRetries = (task?.metadata?.maxRetries as number) ?? 3;
  return !!task && task.status === 'failed' && retryCount < maxRetries;
}

Try / catch

try {
  await taskManager.retryTask(taskId);
} catch (e) {
  if (e instanceof Error && e.message.includes('exceeded max retries')) {
    // budget exhausted: create a fresh task or escalate to a human
  } else throw e;
}

Prevention

When it happens

Trigger: Calling retryTask(taskId) on a task whose metadata.retryCount has been incremented to metadata.maxRetries (default 3) by previous retries; a task created with an explicitly small metadata.maxRetries (e.g. 1) failing twice.

Common situations: Automated retry loops exhausting the default 3 attempts on flaky external dependencies; maxRetries set too low at createTask() time for the workload's real failure rate; code assuming retryTask always succeeds.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/dd544d15e666f074. Report an issue: GitHub.