eyaltoledano/claude-task-master · error
Workflow has been aborted
Error message
Workflow has been aborted
What it means
WorkflowOrchestrator.transition() refuses to process any event except ABORT once the workflow has been aborted — the aborted flag latches and the state machine is terminal. This prevents events (e.g. PHASE_COMPLETE, COMMIT) from mutating an aborted workflow's state.
Source
Thrown at packages/tm-core/src/modules/workflow/orchestrators/workflow-orchestrator.ts:104
return this.context.currentTDDPhase || 'RED';
}
return undefined;
}
/**
* Get workflow context
*/
getContext(): WorkflowContext {
return { ...this.context };
}
/**
* Transition to next state based on event
*/
async transition(event: WorkflowEvent): Promise<void> {
// Check if workflow is aborted
if (this.aborted && event.type !== 'ABORT') {
throw new Error('Workflow has been aborted');
}
// Handle special events that work across all phases
if (event.type === 'ERROR') {
this.handleError(event.error);
await this.triggerAutoPersist();
return;
}
if (event.type === 'ABORT') {
this.aborted = true;
await this.triggerAutoPersist();
return;
}
if (event.type === 'RETRY') {
this.handleRetry();
await this.triggerAutoPersist();View on GitHub (pinned to c0c98d367c)
Solutions
- Stop dispatching events to this orchestrator; treat it as terminal.
- If the workflow should continue, create a new orchestrator instance (or resume from persisted state) instead of reusing the aborted one.
- If you intended to shut down, send the explicit ABORT event, which is the only permitted event.
- Audit code paths that call abortWorkflow() concurrently with in-flight transitions; guard callbacks with an isAborted check.
Example fix
// before
await orchestrator.abortWorkflow();
await orchestrator.transition({ type: 'PHASE_COMPLETE' }); // throws
// after
await orchestrator.abortWorkflow();
const fresh = new WorkflowOrchestrator(/* restored state */);
await fresh.startWorkflow(/* ... */); Defensive patterns
Strategy: try-catch
Validate before calling
// before dispatching
if (orchestrator.isAborted?.() ?? abortedFlag) {
throw new SkipDispatchError('workflow already aborted');
} Type guard
function canDispatch(o, event) {
return !(o.isAborted?.() ?? o.aborted) || event.type === 'ABORT';
} Try / catch
try {
await orchestrator.transition(event);
} catch (e) {
if (e.message === 'Workflow has been aborted') {
// treat as terminal: stop dispatching, optionally start new orchestrator
return;
}
throw e;
} Prevention
- Track aborted state in the caller and stop event loops when set
- Guard async callbacks with an isAborted check before firing transitions
- Never reuse an aborted orchestrator; create or resume a new one
- Use the ABORT event for intentional shutdown paths
When it happens
Trigger: Dispatching any non-ABORT WorkflowEvent via transition() (directly or through startWorkflow, completePhase, commit, finalizeWorkflow) after abortWorkflow() was called or the aborted flag was otherwise set.
Common situations: A caller keeps a stale orchestrator reference and continues driving it after an abort; an async callback queued a phase-complete event that fires after a concurrent abort; retrying work on an aborted workflow instead of starting a fresh one.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot finalize workflow in ${phase} phase. Complete all sub
- Invalid transition: ${event.type} from ${this.currentPhase}
- Invalid transition: RED_PHASE_COMPLETE from non-RED phase
- Invalid transition: GREEN_PHASE_COMPLETE from non-GREEN phas
- Invalid transition: COMMIT_COMPLETE from non-COMMIT phase
AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29).
Data as JSON: /api/errors/972e971bdfdb6b3f.
Report an issue: GitHub.