ruvnet/ruflo · error · Error
Can only recover from error state
Error message
Can only recover from error state
What it means
Agent.recover() clears the error state and returns the agent to 'idle' (deleting the recorded lastError metadata), but it is only legal when status === 'error'. This error means recover() was called on a healthy or otherwise-occupied agent — recovery is the single exit from 'error', not a general reset.
Source
Thrown at v3/@claude-flow/swarm/src/domain/entities/agent.ts:238
/**
* Mark agent as having an error
*/
setError(errorMessage?: string): void {
this._status = 'error';
if (errorMessage) {
this._metadata['lastError'] = errorMessage;
this._metadata['lastErrorAt'] = new Date().toISOString();
}
this._updatedAt = new Date();
}
/**
* 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);View on GitHub (pinned to fa13ee4ad6)
Solutions
- Filter to status === 'error' before calling recover() (fail() is what puts an agent into 'error') After recover(), the agent is 'idle' — call start() to make it active Do not use recover() as a generic reset; the valid pre-states are exactly ['error'] For terminated agents, spawn a replacement instead
Example fix
// before
setInterval(() => agents.forEach(a => a.recover()), 30_000); // throws on healthy agents
// after
setInterval(() => {
agents.filter(a => a.status === 'error').forEach(a => { a.recover(); a.start(); });
}, 30_000); Defensive patterns
Strategy: type-guard
Validate before calling
if (agent.status === 'error') { agent.recover(); agent.start(); } Type guard
function isRecoverable(a) { return a.status === 'error'; } Try / catch
try { agent.recover(); }
catch (e) {
if (e instanceof Error && e.message === 'Can only recover from error state') return; // healthy — nothing to do
throw e;
} Prevention
- Only enter recovery from monitoring that observed status === 'error' (set by fail()) Don't use recover() as a generic reset button Follow recover() with start() — it lands the agent in 'idle'
When it happens
Trigger: Calling recover() on an 'active'/'busy' agent as a defensive reset before it ever failed Double recovery: first recover() succeeds (status now 'idle'), a second recover() throws Recovery sweeps over all agents that don't filter on status === 'error' Calling recover() on a 'terminated' agent instead of creating a new one
Common situations: Automated health-check loops that 'recover just in case' on every tick Playbooks running recover() -> start() blindly after incidents, even for agents that never errored UI 'reset agent' buttons mapped to recover() regardless of state
Related errors
- Cannot start terminated agent
- Can only pause active or busy agent
- Can only resume paused agent
- Cannot assign task to terminated agent
- Task ${taskId} not assigned to this agent
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f6afe3ea1ee0169d.
Report an issue: GitHub.