ruvnet/ruflo · error · Error

Cannot assign task to terminated agent

Error message

Cannot assign task to terminated agent

What it means

Agent.assignTask(taskId) refuses new work when the agent's status is 'terminated'. Termination is terminal and cleared the agent's task set, so assigning work to it is a lifecycle violation rather than a capacity issue.

Source

Thrown at v3/@claude-flow/swarm/src/domain/entities/agent.ts:250

  /**
   * Recover from error state
   */
  recover(): void {
    if (this._status !== 'error') {
      throw new Error('Can only recover from error state');
    }
    this._status = 'idle';
    delete this._metadata['lastError'];
    this._updatedAt = new Date();
  }

  /**
   * Assign a task to this agent
   */
  assignTask(taskId: string): void {
    if (this._status === 'terminated') {
      throw new Error('Cannot assign task to terminated agent');
    }
    if (this._currentTaskIds.size >= this._maxConcurrentTasks) {
      throw new Error('Agent at maximum concurrent task capacity');
    }

    this._currentTaskIds.add(taskId);
    this._status = 'busy';
    this._lastActiveAt = new Date();
    this._updatedAt = new Date();
  }

  /**
   * Complete a task
   */
  completeTask(taskId: string): void {
    if (!this._currentTaskIds.has(taskId)) {
      throw new Error(`Task ${taskId} not assigned to this agent`);
    }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Check agent.status !== 'terminated' before assignTask() (also covers the capacity case separately) Remove terminated agents from scheduling pools when terminate() is called Re-assign the task to another agent or create a replacement agent when the target is terminated Serialize terminate/dispatch per agent (e.g., per-agent mutex or command queue) to avoid the race

Example fix

// before
const agent = pool.pickAny();
agent.assignTask(task.id); // throws if agent was terminated

// after
const agent = pool.pick(a => a.status !== 'terminated' && a.status !== 'paused');
if (!agent) throw new Error('no eligible agent');
agent.assignTask(task.id);
Defensive patterns

Strategy: type-guard

Validate before calling

if (agent.status === 'terminated') {
  agent = pickEligibleAgent(pool); // or spawn one
}
agent.assignTask(taskId);

Type guard

const acceptsWork = (a) => a.status !== 'terminated' && a.status !== 'paused';
// (also check capacity separately before assignTask)

Try / catch

try { agent.assignTask(taskId); }
catch (e) {
  if (e instanceof Error && /terminated agent|maximum concurrent/.test(e.message)) {
    return reassignElsewhere(taskId); // pick another agent or requeue
  }
  throw e;
}

Prevention

When it happens

Trigger: A scheduler assigning from a stale agent list after the agent was terminated (e.g., during scale-down) assignTask() racing an operator-initiated terminate() Rehydrated agent persisted as 'terminated' but still present in the coordinator's available-agents index Queue workers that never remove terminated agents from their pool

Common situations: Agent pool updated by membership events that arrive after task dispatch decisions were made Crash-recovery replays assigning old tasks to agents that terminate() had already retired Dynamic scaling where termination and task assignment come from different components

Related errors


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