ruvnet/ruflo · error
Can only fail running or assigned tasks
Error message
Can only fail running or assigned tasks
What it means
Task.fail(error) accepts only 'running' or 'assigned' tasks. It records the error, increments the retry count, and either marks the task 'failed' (retries exhausted) or resets it to 'queued' for another attempt. This throw means fail() was invoked on a task outside those states — e.g., double-failing, or failing pending/finished tasks.
Source
Thrown at v3/@claude-flow/swarm/src/domain/entities/task.ts:218
/**
* Complete the task successfully
*/
complete(output?: unknown): void {
if (this._status !== 'running') {
throw new Error('Can only complete running tasks');
}
this._status = 'completed';
this._output = output;
this._completedAt = new Date();
}
/**
* Mark task as failed
*/
fail(error: string): void {
if (this._status !== 'running' && this._status !== 'assigned') {
throw new Error('Can only fail running or assigned tasks');
}
this._error = error;
this._retryCount++;
if (this._retryCount >= this._maxRetries) {
this._status = 'failed';
this._completedAt = new Date();
} else {
// Reset for retry
this._status = 'queued';
this._assignedAgentId = undefined;
}
}
/**
* Cancel the task
*/
cancel(): void {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Gate on task.status === 'running' || task.status === 'assigned' before fail() For pre-queue validation errors, reject at the boundary (don't create the task, or use cancel()) instead of fail() Make failure reporting idempotent — treat a second fail() on the same attempt as a no-op Break complete/fail races with single-flight state transitions per task (lock or version number)
Example fix
// before
catch (e) { task.fail(e.message); } // throws if already failed/queued/cancelled
// after
catch (e) {
if (task.status === 'running' || task.status === 'assigned') task.fail(e.message);
else log.warn('stale failure for task %s in state %s', task.id, task.status);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (task.status === 'running' || task.status === 'assigned') task.fail(err.message); else if (task.status === 'pending' || task.status === 'queued') task.cancel(); // intake-time error path else logStaleFailure(task);
Type guard
function isFailable(t) { return t.status === 'running' || t.status === 'assigned'; } Try / catch
try { task.fail(err.message); }
catch (e) {
if (e instanceof Error && e.message === 'Can only fail running or assigned tasks') return; // stale/dup failure
throw e;
} Prevention
- Reject invalid tasks at the boundary (cancel) instead of fail()ing pending ones
- Deduplicate error events; a second fail() on the same attempt should be a no-op
- Break fail/complete races with per-task locks or version numbers
When it happens
Trigger: Reporting failure for a task that is still 'pending'/'queued' (failure before assignment/start, e.g., validation errors at intake) Double fail(): the first fail() reset the task to 'queued' (or 'failed'), the second throws Failing a task after it was cancelled or already completed Retry timer racing a completion: fail() arrives after complete() succeeded
Common situations: Intake-time errors routed into task.fail() instead of a pre-queue rejection path At-least-once error events producing duplicate fail() calls Timeout supervisors failing tasks whose workers already completed them Reconciliation jobs sweeping and failing every task older than X regardless of status
Related errors
- Can only queue pending tasks
- Can only assign queued or pending tasks
- Can only start assigned tasks
- Can only complete running tasks
- Cannot cancel finished tasks
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/eea299a59b546794.
Report an issue: GitHub.