Compare commits

...

3 Commits

Author SHA1 Message Date
Devin AI
352b213f7f fix: remove unused imports in test_rich_live_cleanup.py
Removes unused imports that were causing lint failures:
- logging
- io.StringIO
- unittest.mock.MagicMock
- rich.logging.RichHandler

This fixes the CI lint check failure (job 45782458300).

Co-Authored-By: Jo\u00E3o <joao@crewai.com>
2025-07-11 08:08:34 +00:00
Devin AI
07f4e47bb6 fix: regenerate uv.lock to resolve parsing errors
The previous uv.lock file had TOML parsing errors that prevented
running tests and installing dependencies. This regenerates the
lock file with proper formatting.

Co-Authored-By: Jo\u00E3o <joao@crewai.com>
2025-07-11 08:04:44 +00:00
Devin AI
20cdc92142 fix: properly cleanup Rich Live sessions after flow/crew completion
- Add stop_live() method to ConsoleFormatter to clean up Live sessions
- Call cleanup in FlowFinishedEvent and CrewKickoffCompletedEvent handlers
- Add comprehensive tests for Live session cleanup functionality
- Fixes issue #3136 where logging output was suppressed after CrewAI operations

The issue was that Rich Live sessions were not being explicitly stopped
when CrewAI flows or crews completed, leaving the terminal in a state
where subsequent logging output would be suppressed until process exit.

This fix ensures that Live sessions are properly cleaned up by:
1. Adding a stop_live() method that safely stops and clears Live sessions
2. Calling this cleanup method in the appropriate event handlers
3. Adding tests to prevent regression

Resolves #3136

Co-Authored-By: Jo\u00E3o <joao@crewai.com>
2025-07-11 08:04:32 +00:00
5 changed files with 3202 additions and 3228 deletions

View File

@@ -120,6 +120,7 @@ class EventListener(BaseEventListener):
"completed",
final_string_output,
)
self.formatter.stop_live()
@crewai_event_bus.on(CrewKickoffFailedEvent)
def on_crew_failed(source, event: CrewKickoffFailedEvent):
@@ -262,6 +263,7 @@ class EventListener(BaseEventListener):
self.formatter.update_flow_status(
self.formatter.current_flow_tree, event.flow_name, source.flow_id
)
self.formatter.stop_live()
@crewai_event_bus.on(MethodExecutionStartedEvent)
def on_method_execution_started(source, event: MethodExecutionStartedEvent):

View File

@@ -1753,3 +1753,9 @@ class ConsoleFormatter:
Attempts=f"{retry_count + 1}",
)
self.print_panel(content, "🛡️ Guardrail Failed", "red")
def stop_live(self) -> None:
"""Stop and clear any active Live session to restore normal terminal output."""
if self._live:
self._live.stop()
self._live = None

View File

@@ -0,0 +1,54 @@
from unittest.mock import patch
from rich.tree import Tree
from crewai.utilities.events.utils.console_formatter import ConsoleFormatter
from crewai.utilities.events.event_listener import EventListener
class TestRichLiveCleanup:
"""Test that Rich Live sessions are properly cleaned up after CrewAI operations."""
def test_logging_works_after_tree_rendering(self):
"""Test that logging output appears after tree rendering with proper cleanup."""
formatter = ConsoleFormatter()
tree = Tree("Test Flow")
formatter.print(tree)
assert formatter._live is not None
formatter.stop_live()
assert formatter._live is None
with patch.object(formatter.console, 'print') as mock_print:
formatter.print("This should appear immediately")
mock_print.assert_called_once_with("This should appear immediately")
def test_event_listener_cleanup_integration(self):
"""Test that EventListener properly cleans up Live sessions."""
event_listener = EventListener()
formatter = event_listener.formatter
tree = Tree("Test Crew")
formatter.print(tree)
assert formatter._live is not None
formatter.stop_live()
assert formatter._live is None
def test_stop_live_restores_normal_output(self):
"""Test that stop_live properly restores normal console output behavior."""
formatter = ConsoleFormatter()
tree = Tree("Test Tree")
formatter.print(tree)
assert formatter._live is not None
formatter.stop_live()
assert formatter._live is None
with patch.object(formatter.console, 'print') as mock_print:
formatter.print("Normal output")
mock_print.assert_called_once_with("Normal output")

View File

@@ -114,3 +114,38 @@ class TestConsoleFormatterPauseResume:
assert hasattr(formatter, '_live_paused')
assert not formatter._live_paused
def test_stop_live_with_active_session(self):
"""Test stopping Live session when one is active."""
formatter = ConsoleFormatter()
mock_live = MagicMock(spec=Live)
formatter._live = mock_live
formatter.stop_live()
mock_live.stop.assert_called_once()
assert formatter._live is None
def test_stop_live_with_no_session(self):
"""Test stopping Live session when none exists."""
formatter = ConsoleFormatter()
formatter._live = None
formatter.stop_live()
assert formatter._live is None
def test_stop_live_multiple_calls(self):
"""Test multiple calls to stop_live are safe."""
formatter = ConsoleFormatter()
mock_live = MagicMock(spec=Live)
formatter._live = mock_live
formatter.stop_live()
formatter.stop_live()
mock_live.stop.assert_called_once()
assert formatter._live is None

6333
uv.lock generated

File diff suppressed because it is too large Load Diff