eyaltoledano/claude-task-master · error

Invalid transition: ${event.type} from ${this.currentPhase}

Error message

Invalid transition: ${event.type} from ${this.currentPhase}

What it means

The orchestrator validates each event against its transition table, matching (from=currentPhase, event=type). If no transition is registered for the current phase/event pair, transition() throws this error instead of silently ignoring the event — the state machine only allows explicitly declared moves.

Source

Thrown at packages/tm-core/src/modules/workflow/orchestrators/workflow-orchestrator.ts:139

			this.handleRetry();
			await this.triggerAutoPersist();
			return;
		}

		// Handle TDD phase transitions within SUBTASK_LOOP
		if (this.currentPhase === 'SUBTASK_LOOP') {
			await this.handleTDDPhaseTransition(event);
			await this.triggerAutoPersist();
			return;
		}

		// Handle main workflow phase transitions
		const validTransition = this.transitions.find(
			(t) => t.from === this.currentPhase && t.event === event.type
		);

		if (!validTransition) {
			throw new Error(
				`Invalid transition: ${event.type} from ${this.currentPhase}`
			);
		}

		// Execute transition
		this.executeTransition(validTransition, event);
		await this.triggerAutoPersist();
	}

	/**
	 * Handle TDD phase transitions (RED -> GREEN -> COMMIT)
	 */
	private async handleTDDPhaseTransition(event: WorkflowEvent): Promise<void> {
		const currentTDD = this.context.currentTDDPhase || 'RED';

		switch (event.type) {
			case 'RED_PHASE_COMPLETE':
				if (currentTDD !== 'RED') {

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Log this.currentPhase and the event type from the message to see exactly which move is illegal.
  2. Check the orchestrator's phase before dispatching (e.g. only send COMPLETE_PHASE when in an active phase).
  3. Deduplicate/serialize event dispatch — don't fire transition() concurrently from multiple code paths.
  4. If starting fresh, ensure startWorkflow is called only once per orchestrator instance.
  5. Verify the transitions table includes the intended edge if you extended the state machine.

Example fix

// before
await orchestrator.transition({ type: 'COMPLETE_PHASE' }); // maybe already done
// after
if (orchestrator.currentPhase !== 'COMPLETE') {
  await orchestrator.transition({ type: 'COMPLETE_PHASE' });
}
Defensive patterns

Strategy: validation

Validate before calling

const allowed = orchestrator.transitions
  .filter((t) => t.from === orchestrator.currentPhase)
  .map((t) => t.event);
if (!allowed.includes(event.type)) {
  throw new SkipDispatchError(`${event.type} illegal from ${orchestrator.currentPhase}`);
}

Type guard

function isLegalTransition(o, event) {
  return o.transitions.some(
    (t) => t.from === o.currentPhase && t.event === event.type
  );
}

Try / catch

try {
  await orchestrator.transition(event);
} catch (e) {
  if (e.message.startsWith('Invalid transition:')) {
    console.warn(`skipped ${event.type}: ${e.message}`);
    return; // or resync state
  }
  throw e;
}

Prevention

When it happens

Trigger: Dispatching an event that is not valid for the current phase, e.g. START_WORKFLOW when already initialized, COMPLETE_PHASE out of order, COMMIT in a non-committable phase, or duplicate events already consumed by a prior transition.

Common situations: Calling startWorkflow() twice; firing COMPLETE_PHASE twice for one phase; events racing so two callers transition the same orchestrator concurrently; replaying a persisted event log against an already-advanced state.

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/02f874938c233961. Report an issue: GitHub.