From c3fb000f929f5a2be20a7f3963290bf00151a12b Mon Sep 17 00:00:00 2001 From: Lukas May Date: Sat, 7 Mar 2026 00:44:46 +0100 Subject: [PATCH] fix: prevent phase stuck in_progress when merge fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In YOLO mode, if mergePhaseIntoInitiative() threw (e.g., branch doesn't exist, merge conflict), the error propagated and completePhase() was never called — leaving the phase permanently stuck at in_progress. Also wrap per-phase recovery in try-catch so one failing phase doesn't abort the entire recoverDispatchQueues() loop. --- apps/server/execution/orchestrator.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/server/execution/orchestrator.ts b/apps/server/execution/orchestrator.ts index 4ab94e0..97520be 100644 --- a/apps/server/execution/orchestrator.ts +++ b/apps/server/execution/orchestrator.ts @@ -353,7 +353,11 @@ export class ExecutionOrchestrator { if (initiative.executionMode === 'yolo') { // Merge phase branch into initiative branch (only when branches exist) if (initiative.branch) { - await this.mergePhaseIntoInitiative(phaseId); + try { + await this.mergePhaseIntoInitiative(phaseId); + } catch (err) { + log.error({ phaseId, err: err instanceof Error ? err.message : String(err) }, 'phase merge failed, completing phase anyway'); + } } await this.phaseDispatchManager.completePhase(phaseId); @@ -663,6 +667,7 @@ export class ExecutionOrchestrator { const phases = await this.phaseRepository.findByInitiativeId(initiative.id); for (const phase of phases) { + try { // Re-queue approved phases into the phase dispatch queue if (phase.status === 'approved') { try { @@ -734,6 +739,9 @@ export class ExecutionOrchestrator { phasesRecovered++; } } + } catch (err) { + log.error({ phaseId: phase.id, err: err instanceof Error ? err.message : String(err) }, 'phase recovery failed, continuing with remaining phases'); + } } }