eyaltoledano/claude-task-master · error

Invalid transition: COMMIT_COMPLETE from non-COMMIT phase

Error message

Invalid transition: COMMIT_COMPLETE from non-COMMIT phase

What it means

COMMIT is the final phase of each subtask's TDD cycle (after GREEN passes). This error is thrown by handleTDDPhaseTransition when a COMMIT_COMPLETE event is dispatched while the current TDD phase is not 'COMMIT' — the GREEN phase has not been completed (or the cycle already moved on). It preserves the invariant that a subtask can only be marked completed after tests passed and the GREEN -> COMMIT step occurred.

Source

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

					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');
				this.context.currentTDDPhase = 'COMMIT';
				this.emit('tdd:commit:started');
				break;

			case 'COMMIT_COMPLETE':
				if (currentTDD !== 'COMMIT') {
					throw new Error(
						'Invalid transition: COMMIT_COMPLETE from non-COMMIT phase'
					);
				}
				this.emit('tdd:commit:completed');
				// Mark current subtask as completed
				const currentSubtask =
					this.context.subtasks[this.context.currentSubtaskIndex];
				if (currentSubtask) {
					currentSubtask.status = 'completed';
				}
				break;

			case 'SUBTASK_COMPLETE':
				this.emit('subtask:completed');
				// Move to next subtask
				this.context.currentSubtaskIndex++;

				// Emit progress update

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Verify the phase is 'COMMIT' before dispatching; if still in GREEN, first complete GREEN with passing testResults via GREEN_PHASE_COMPLETE.
  2. If already committed, do not send COMMIT_COMPLETE again — send SUBTASK_COMPLETE (or wait for the orchestrator's own flow) instead.
  3. If the orchestrator advanced to a new subtask, restart its cycle at RED_PHASE_COMPLETE rather than sending COMMIT_COMPLETE.
  4. Check whether events are being replayed/duplicated by your dispatch layer and add idempotency guards.

Example fix

// before
await orchestrator.transition({ type: 'COMMIT_COMPLETE' });

// after
const ctx = orchestrator.getContext();
if (ctx.currentTDDPhase === 'COMMIT') {
  await orchestrator.transition({ type: 'COMMIT_COMPLETE' });
} else if (ctx.currentTDDPhase === 'GREEN') {
  await orchestrator.transition({ type: 'GREEN_PHASE_COMPLETE', testResults });
  await orchestrator.transition({ type: 'COMMIT_COMPLETE' });
}
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try {
  await orchestrator.transition({ type: 'COMMIT_COMPLETE' });
} catch (e) {
  if (e instanceof Error && e.message.includes('COMMIT_COMPLETE from non-COMMIT')) {
    // inspect orchestrator.getContext().currentTDDPhase and resume the cycle from the correct event
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling transition({ type: 'COMMIT_COMPLETE' }) while currentTDDPhase is 'RED' or 'GREEN'; sending COMMIT_COMPLETE twice (second time the phase has moved on); dispatching COMMIT_COMPLETE right after SUBTASK_COMPLETE started the next subtask (phase reset to RED).

Common situations: Automations that try to shortcut the cycle by jumping straight to commit; retry logic re-sending COMMIT_COMPLETE after a transient failure; event replay from a queue delivering COMMIT_COMPLETE before GREEN_PHASE_COMPLETE.

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