@@ -243,21 +243,25 @@ def setup_project(
243243 cmd_logger , cmd_logger_id = create_command_logger ("project" , project_name )
244244 cmd_logger .info (f"Starting project setup: { project_name } (docker_label={ docker_label } )" )
245245
246- # Initialize UI Manager if in UI mode
247- if self .config .ui_mode :
248- self .ui_manager = UIManager (project_name = project_name , console = self .console )
249- self .ui_manager .start ()
250- else :
251- self .console .print (
252- Panel .fit (
253- f"[bold blue]Setting up project: { project_name } [/bold blue]\n "
254- f"[dim]Repository: { project_url } [/dim]\n "
255- f"[dim]Goal: { goal } [/dim]" ,
256- border_style = "blue" ,
257- )
258- )
246+ # Track whether we already displayed the final UI summary so the
247+ # cleanup `finally` below doesn't re-display or skip it incorrectly.
248+ final_summary_shown = False
259249
260250 try :
251+ # Initialize UI Manager if in UI mode
252+ if self .config .ui_mode :
253+ self .ui_manager = UIManager (project_name = project_name , console = self .console )
254+ self .ui_manager .start ()
255+ else :
256+ self .console .print (
257+ Panel .fit (
258+ f"[bold blue]Setting up project: { project_name } [/bold blue]\n "
259+ f"[dim]Repository: { project_url } [/dim]\n "
260+ f"[dim]Goal: { goal } [/dim]" ,
261+ border_style = "blue" ,
262+ )
263+ )
264+
261265 # Step 1: Setup Docker environment
262266 if self .config .ui_mode :
263267 self .ui_manager .handle_event (UIEvent (
@@ -389,30 +393,54 @@ def setup_project(
389393 ))
390394 # Display final summary and stop UI manager
391395 self .ui_manager .display_final_summary ()
396+ final_summary_shown = True
392397 else :
393398 # Provide traditional summary
394399 self ._provide_setup_summary (success )
395400
396401 cmd_logger .info (f"Project setup completed: success={ success } " )
397-
398- # Cleanup command logger
399- session_logger = get_session_logger ()
400- if session_logger :
401- session_logger .cleanup_command_logger (cmd_logger_id )
402-
403402 return success
404403
405404 except Exception as e :
406405 cmd_logger .error (f"Setup failed: { e } " , exc_info = True )
407406 # Always show critical errors
408407 self .console .print (f"[bold red]❌ Setup failed: { e } [/bold red]" )
408+ return False
409409
410- # Cleanup command logger
410+ finally :
411+ # Tear down the live UI on every exit path (early returns, exceptions,
412+ # normal completion). If the happy-path branch above didn't get to
413+ # `display_final_summary`, do it now so the user sees the failure
414+ # state instead of a frozen spinner.
415+ if self .config .ui_mode and self .ui_manager and not final_summary_shown :
416+ # Any phase still in 'running' was aborted, not completed.
417+ for phase , data in self .ui_manager .phases_data .items ():
418+ if data .get ("status" ) == "running" :
419+ try :
420+ self .ui_manager .handle_event (UIEvent (
421+ event_type = EventType .PHASE_ERROR ,
422+ message = "Phase aborted" ,
423+ phase = phase ,
424+ level = "error" ,
425+ ))
426+ except Exception :
427+ pass
428+ try :
429+ self .ui_manager .display_final_summary ()
430+ except Exception :
431+ # display_final_summary itself failed; at minimum stop Live.
432+ try :
433+ self .ui_manager .stop ()
434+ except Exception :
435+ pass
436+
437+ # Always release the command-specific loguru handler.
411438 session_logger = get_session_logger ()
412439 if session_logger :
413- session_logger .cleanup_command_logger (cmd_logger_id )
414-
415- return False
440+ try :
441+ session_logger .cleanup_command_logger (cmd_logger_id )
442+ except Exception :
443+ pass
416444
417445 def continue_project (self , project_name : str , additional_request : Optional [str ] = None ) -> bool :
418446 """Continue working on an existing project."""
@@ -486,20 +514,24 @@ def run_task(self, project_name: str, task_description: str) -> bool:
486514 cmd_logger , cmd_logger_id = create_command_logger ("run" , project_name )
487515 cmd_logger .info (f"Starting task execution: { task_description } " )
488516
489- # Initialize UI Manager if in UI mode
490- if self .config .ui_mode :
491- self .ui_manager = UIManager (project_name = project_name , console = self .console )
492- self .ui_manager .start ()
493- else :
494- self .console .print (
495- Panel .fit (
496- f"[bold cyan]Running task on: { project_name } [/bold cyan]\n "
497- f"[dim]Task: { task_description } [/dim]" ,
498- border_style = "cyan" ,
499- )
500- )
517+ # Track whether we already displayed the final UI summary so the
518+ # cleanup `finally` below doesn't re-display or skip it incorrectly.
519+ final_summary_shown = False
501520
502521 try :
522+ # Initialize UI Manager if in UI mode
523+ if self .config .ui_mode :
524+ self .ui_manager = UIManager (project_name = project_name , console = self .console )
525+ self .ui_manager .start ()
526+ else :
527+ self .console .print (
528+ Panel .fit (
529+ f"[bold cyan]Running task on: { project_name } [/bold cyan]\n "
530+ f"[dim]Task: { task_description } [/dim]" ,
531+ border_style = "cyan" ,
532+ )
533+ )
534+
503535 # Step 1: Ensure Docker container is running
504536 if self .config .ui_mode :
505537 self .ui_manager .handle_event (UIEvent (
@@ -626,33 +658,58 @@ def run_task(self, project_name: str, task_description: str) -> bool:
626658 # Step 7: Provide execution summary
627659 if self .config .ui_mode :
628660 self .ui_manager .display_final_summary ()
661+ final_summary_shown = True
629662 else :
630663 self ._provide_task_summary (success , task_description )
631664
632665 cmd_logger .info (f"Task execution completed: success={ success } " )
633-
634- # Cleanup command logger
635- session_logger = get_session_logger ()
636- if session_logger :
637- session_logger .cleanup_command_logger (cmd_logger_id )
638-
639666 return success
640667
641668 except Exception as e :
642669 cmd_logger .error (f"Task execution failed: { e } " , exc_info = True )
643670 # Always show critical errors
644671 self .console .print (f"[bold red]❌ Task execution failed: { e } [/bold red]" )
645672
646- # Update last comment with error
647- error_comment = f"Task failed: { task_description } - Error: { str (e )[:100 ]} "
673+ # Best-effort: record the failure on the container's last-comment marker.
674+ try :
675+ error_comment = f"Task failed: { task_description } - Error: { str (e )[:100 ]} "
676+ self .orchestrator .update_last_comment (error_comment )
677+ except Exception :
678+ pass
679+
680+ return False
681+
682+ finally :
683+ # Tear down the live UI on every exit path. Without this, early
684+ # `return False` paths above would leave Rich's Live thread
685+ # refreshing the terminal indefinitely and swallowing any error
686+ # message printed afterwards.
687+ if self .config .ui_mode and self .ui_manager and not final_summary_shown :
688+ for phase , data in self .ui_manager .phases_data .items ():
689+ if data .get ("status" ) == "running" :
690+ try :
691+ self .ui_manager .handle_event (UIEvent (
692+ event_type = EventType .PHASE_ERROR ,
693+ message = "Phase aborted" ,
694+ phase = phase ,
695+ level = "error" ,
696+ ))
697+ except Exception :
698+ pass
699+ try :
700+ self .ui_manager .display_final_summary ()
701+ except Exception :
702+ try :
703+ self .ui_manager .stop ()
704+ except Exception :
705+ pass
648706
649- # Cleanup command logger
650707 session_logger = get_session_logger ()
651708 if session_logger :
652- session_logger . cleanup_command_logger ( cmd_logger_id )
653- self . orchestrator . update_last_comment ( error_comment )
654-
655- return False
709+ try :
710+ session_logger . cleanup_command_logger ( cmd_logger_id )
711+ except Exception :
712+ pass
656713
657714 def _setup_docker_environment (self , project_name : str ) -> bool :
658715 """Setup the Docker environment for the project."""
0 commit comments