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

  1. Stop dispatching events to this orchestrator; treat it as terminal.
  2. If the workflow should continue, create a new orchestrator instance (or resume from persisted state) instead of reusing the aborted one.
  3. If you intended to shut down, send the explicit ABORT event, which is the only permitted event.
  4. 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

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


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/972e971bdfdb6b3f. Report an issue: GitHub.