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
- 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
- Remove terminated agents from scheduling pools at terminate() time
- Re-check agent status at dispatch time, not from a cached list
- Serialize terminate() and assign() per agent to avoid the race
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
- Cannot start terminated agent
- Can only pause active or busy agent
- Can only resume paused agent
- Can only recover from error state
- Agent at maximum concurrent task capacity
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/5676ecdcd078ed15.
Report an issue: GitHub.