eyaltoledano/claude-task-master · error

Invalid transition: GREEN_PHASE_COMPLETE from non-GREEN phas

Error message

Invalid transition: GREEN_PHASE_COMPLETE from non-GREEN phase

What it means

The TDD workflow orchestrator enforces the strict RED -> GREEN -> COMMIT cycle per subtask. This error is thrown in handleTDDPhaseTransition when a GREEN_PHASE_COMPLETE event arrives while context.currentTDDPhase is not 'GREEN' (e.g. still RED or COMMIT). The orchestrator tracks the current TDD phase internally, and completing the GREEN phase out of order would break the state machine invariant that tests were written (RED) before implementation (GREEN).

Source

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

						this.context.currentTDDPhase = 'RED';
						this.emit('tdd:red:started');
						this.emit('subtask:started');
					} else {
						// All subtasks complete, transition to FINALIZE
						await this.transition({ type: 'ALL_SUBTASKS_COMPLETE' });
					}
					break;
				}

				// Normal RED phase: has failing tests, proceed to GREEN
				this.emit('tdd:red:completed');
				this.context.currentTDDPhase = 'GREEN';
				this.emit('tdd:green:started');
				break;

			case 'GREEN_PHASE_COMPLETE':
				if (currentTDD !== 'GREEN') {
					throw new Error(
						'Invalid transition: GREEN_PHASE_COMPLETE from non-GREEN phase'
					);
				}

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

				// Validate GREEN phase has no failures
				if (event.testResults.failed !== 0) {
					throw new Error('GREEN phase must have zero failures');
				}

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

				this.emit('tdd:green:completed');

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Check the current phase before transitioning: only send GREEN_PHASE_COMPLETE when context.currentTDDPhase === 'GREEN'.
  2. If still in RED, first emit RED_PHASE_COMPLETE (with test results showing at least one failing test) so the orchestrator moves to GREEN.
  3. If in COMMIT, the GREEN phase was already completed — do not re-send GREEN_PHASE_COMPLETE; send COMMIT_COMPLETE instead.
  4. For a new subtask, phases restart at RED; send RED_PHASE_COMPLETE first, not GREEN_PHASE_COMPLETE.
  5. Inspect emitted events ('tdd:green:started') or logged state to confirm the phase actually advanced before sending the completion event.

Example fix

// before
await orchestrator.transition({ type: 'GREEN_PHASE_COMPLETE', testResults });

// after
if (orchestrator.getContext().currentTDDPhase === 'GREEN') {
  await orchestrator.transition({ type: 'GREEN_PHASE_COMPLETE', testResults });
} else {
  // advance through RED first
  await orchestrator.transition({ type: 'RED_PHASE_COMPLETE', testResults });
}
Defensive patterns

Strategy: validation

Validate before calling

const ctx = orchestrator.getContext();
if (ctx.currentTDDPhase !== 'GREEN') {
  throw new Error(`Cannot complete GREEN phase: current TDD phase is ${ctx.currentTDDPhase ?? 'RED'}`);
}

Type guard

function canCompleteGreen(ctx: { currentTDDPhase?: 'RED' | 'GREEN' | 'COMMIT' }): ctx is { currentTDDPhase: 'GREEN' } {
  return ctx.currentTDDPhase === 'GREEN';
}

Try / catch

try {
  await orchestrator.transition({ type: 'GREEN_PHASE_COMPLETE', testResults });
} catch (e) {
  if (e instanceof Error && e.message.includes('GREEN_PHASE_COMPLETE from non-GREEN')) {
    // re-sync: inspect orchestrator.getContext().currentTDDPhase and resume from the correct event
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling orchestrator.transition({ type: 'GREEN_PHASE_COMPLETE', testResults }) when currentTDDPhase is 'RED' (skipping RED_PHASE_COMPLETE), or when it is 'COMMIT' (double-completing GREEN), or after phase was reset to undefined/RED (e.g. after ALL_SUBTASKS_COMPLETE or SUBTASK_COMPLETE moved to a new subtask).

Common situations: Driving the orchestrator from custom automation scripts instead of the normal TDD flow; replaying or re-dispatching events after a retry causes duplicate GREEN_PHASE_COMPLETE; resuming a persisted workflow whose phase was restored incorrectly; assuming phases can be skipped because tests already pass.

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