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
- Create a replacement task via createTask() with fresh metadata (retryCount: 0) instead of retrying the exhausted one
- If failures are genuinely transient, set a higher metadata.maxRetries when creating the task
- 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
- Compare metadata.retryCount against metadata.maxRetries before calling retryTask
- Set maxRetries at createTask() based on the dependency's real flakiness
- Never loop retryTask() unconditionally — always bound the budget
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
- SSRF guard: private/loopback host rejected — ${host}
- JSON.stringify(response.error)
- Failed to create share link
- User not found
- Task not found: ${taskId}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/dd544d15e666f074.
Report an issue: GitHub.