eyaltoledano/claude-task-master · error

Invalid transition: RED_PHASE_COMPLETE from non-RED phase

Error message

Invalid transition: RED_PHASE_COMPLETE from non-RED phase

What it means

TDD sub-phase events are handled by handleTDDPhaseTransition(). RED_PHASE_COMPLETE is only legal when the workflow's currentTDDPhase is 'RED'; dispatching it from GREEN or REFACTOR throws this fixed-message error, since a RED-phase completion implies test results concluding the RED phase.

Source

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

				`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') {
					throw new Error(
						'Invalid transition: RED_PHASE_COMPLETE from non-RED phase'
					);
				}

				// Validate test results are provided
				if (!event.testResults) {
					throw new Error('Test results required for RED phase transition');
				}

				// Store test results in context
				this.context.lastTestResults = event.testResults;

				// Special case: All tests passing in RED phase means feature already implemented
				if (event.testResults.failed === 0) {
					this.emit('tdd:red:completed');
					this.emit('tdd:feature-already-implemented', {
						subtaskId: this.getCurrentSubtaskId(),
						testResults: event.testResults

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Check context.currentTDDPhase before dispatching; only send RED_PHASE_COMPLETE from RED.
  2. If the RED phase already completed, send the next appropriate event (e.g. GREEN/REFACTOR phase events) instead.
  3. Deduplicate test-runner callbacks so completion fires once.
  4. When resuming from persisted state, confirm currentTDDPhase was persisted correctly before replaying events.

Example fix

// before
await orchestrator.transition({ type: 'RED_PHASE_COMPLETE', testResults });
// after
if ((orchestrator.context.currentTDDPhase ?? 'RED') === 'RED') {
  await orchestrator.transition({ type: 'RED_PHASE_COMPLETE', testResults });
}
Defensive patterns

Strategy: validation

Validate before calling

const tdd = orchestrator.context.currentTDDPhase || 'RED';
if (event.type === 'RED_PHASE_COMPLETE' && tdd !== 'RED') {
  throw new SkipDispatchError('RED_PHASE_COMPLETE illegal outside RED phase');
}

Type guard

function canCompleteRed(ctx, event) {
  return event.type !== 'RED_PHASE_COMPLETE'
    || (ctx.currentTDDPhase || 'RED') === 'RED';
}

Try / catch

try {
  await orchestrator.transition(event);
} catch (e) {
  if (e.message.includes('RED_PHASE_COMPLETE from non-RED phase')) {
    console.warn('RED already completed; dispatch next phase event instead');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Dispatching { type: 'RED_PHASE_COMPLETE' } when context.currentTDDPhase is 'GREEN' or 'REFACTOR' — e.g. double-completing the RED phase, or resuming an orchestrator whose TDD sub-state was already advanced.

Common situations: A test runner callback fires RED_PHASE_COMPLETE twice; restoring state where the RED→GREEN transition already happened; parallel agents each reporting RED completion for the same workflow.

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