fix: always use ephemeral tracing for first-time users

The first-time handler UX is built around ephemeral traces (access
  code, 24hr expiry link, browser open). Checking auth and creating
  non-ephemeral batches caused the handler to fall through to the
  local traces fallback since ephemeral_trace_url is only set for
  ephemeral batches. The server's LinkEphemeralTracesJob links
  ephemeral traces to user accounts on signup regardless.

  Remove auth check from first-time handler and always pass
  use_ephemeral=True to _initialize_backend_batch.
This commit is contained in:
Tiago Freire
2026-03-18 18:48:15 -03:00
parent 08c533905e
commit c00544c97d
4 changed files with 21 additions and 83 deletions

View File

@@ -99,7 +99,7 @@ class FirstTimeTraceHandler:
self.batch_manager._initialize_backend_batch(
user_context=user_context,
execution_metadata=execution_metadata,
use_ephemeral=self.batch_manager.is_current_batch_ephemeral,
use_ephemeral=True,
skip_context_check=True,
)
@@ -109,10 +109,11 @@ class FirstTimeTraceHandler:
self.batch_manager.backend_initialized = True
events_count = len(self.batch_manager.event_buffer)
if self.batch_manager.event_buffer:
self.batch_manager._send_events_to_backend()
self.batch_manager.finalize_batch()
self.batch_manager._finalize_backend_batch(events_count)
self.ephemeral_url = self.batch_manager.ephemeral_trace_url
if not self.ephemeral_url:

View File

@@ -196,6 +196,7 @@ class TraceBatchManager:
)
if response.status_code in [201, 200]:
self.is_current_batch_ephemeral = use_ephemeral
response_data = response.json()
self.trace_batch_id = (
response_data["trace_id"]

View File

@@ -706,14 +706,13 @@ class TraceCollectionListener(BaseEventListener):
user_context: User context information.
execution_metadata: Metadata about the execution.
"""
use_ephemeral = not self._check_authenticated()
if self.first_time_handler.is_first_time:
self.batch_manager.initialize_batch(
user_context, execution_metadata, use_ephemeral=use_ephemeral
user_context, execution_metadata, use_ephemeral=True
)
return
use_ephemeral = not self._check_authenticated()
self.batch_manager.initialize_batch(
user_context, execution_metadata, use_ephemeral=use_ephemeral
)

View File

@@ -1325,7 +1325,7 @@ class TestFirstTimeHandlerBackendInitGuard:
"send_ephemeral_trace_events",
side_effect=capture_send,
),
patch.object(bm, "finalize_batch"),
patch.object(bm, "_finalize_backend_batch"),
):
bm.event_buffer = [MagicMock(to_dict=MagicMock(return_value={}))]
handler._initialize_backend_and_send_events()
@@ -1348,7 +1348,7 @@ class TestFirstTimeHandlerBackendInitGuard:
return_value=None, # server call fails
),
patch.object(bm, "_send_events_to_backend") as mock_send,
patch.object(bm, "finalize_batch") as mock_finalize,
patch.object(bm, "_finalize_backend_batch") as mock_finalize,
patch.object(handler, "_gracefully_fail") as mock_fail,
):
bm.event_buffer = [MagicMock()]
@@ -1377,7 +1377,7 @@ class TestFirstTimeHandlerBackendInitGuard:
return_value=mock_response,
),
patch.object(bm, "_send_events_to_backend") as mock_send,
patch.object(bm, "finalize_batch") as mock_finalize,
patch.object(bm, "_finalize_backend_batch") as mock_finalize,
patch.object(handler, "_gracefully_fail") as mock_fail,
):
bm.event_buffer = [MagicMock()]
@@ -1390,77 +1390,10 @@ class TestFirstTimeHandlerBackendInitGuard:
mock_fail.assert_called_once()
class TestFirstTimeAuthRespected:
"""Tests for Fix 3: first-time users respect authentication status."""
class TestFirstTimeHandlerAlwaysEphemeral:
"""Tests that first-time handler always uses ephemeral with skip_context_check."""
def test_first_time_authenticated_uses_non_ephemeral(self):
"""Authenticated first-time user should use non-ephemeral (use_ephemeral=False)."""
with (
patch(
"crewai.events.listeners.tracing.trace_batch_manager.get_auth_token",
return_value="mock_token",
),
patch(
"crewai.events.listeners.tracing.trace_listener.get_auth_token",
return_value="mock_token",
),
):
from crewai.events.listeners.tracing.trace_listener import TraceCollectionListener
listener = TraceCollectionListener.__new__(TraceCollectionListener)
listener.first_time_handler = FirstTimeTraceHandler()
listener.first_time_handler.is_first_time = True
listener.batch_manager = TraceBatchManager()
with patch.object(
listener.batch_manager, "initialize_batch"
) as mock_init_batch:
listener._initialize_batch(
user_context={"privacy_level": "standard"},
execution_metadata={"execution_type": "crew"},
)
mock_init_batch.assert_called_once_with(
{"privacy_level": "standard"},
{"execution_type": "crew"},
use_ephemeral=False,
)
def test_first_time_unauthenticated_uses_ephemeral(self):
"""Unauthenticated first-time user should use ephemeral (use_ephemeral=True)."""
with (
patch(
"crewai.events.listeners.tracing.trace_batch_manager.get_auth_token",
return_value="mock_token",
),
patch(
"crewai.events.listeners.tracing.trace_listener.get_auth_token",
return_value=None,
),
):
from crewai.events.listeners.tracing.trace_listener import TraceCollectionListener
listener = TraceCollectionListener.__new__(TraceCollectionListener)
listener.first_time_handler = FirstTimeTraceHandler()
listener.first_time_handler.is_first_time = True
listener.batch_manager = TraceBatchManager()
with patch.object(
listener.batch_manager, "initialize_batch"
) as mock_init_batch:
listener._initialize_batch(
user_context={"privacy_level": "standard"},
execution_metadata={"execution_type": "crew"},
)
mock_init_batch.assert_called_once_with(
{"privacy_level": "standard"},
{"execution_type": "crew"},
use_ephemeral=True,
)
def test_first_time_handler_deferred_init_uses_batch_ephemeral_flag(self):
"""Deferred backend init should use is_current_batch_ephemeral, not hardcoded True."""
def _make_handler_with_manager(self):
with patch(
"crewai.events.listeners.tracing.trace_batch_manager.get_auth_token",
return_value="mock_token",
@@ -1471,26 +1404,30 @@ class TestFirstTimeAuthRespected:
execution_metadata={"execution_type": "crew", "crew_name": "test"},
)
bm.trace_batch_id = bm.current_batch.batch_id
bm.is_current_batch_ephemeral = False # authenticated user
bm.is_current_batch_ephemeral = True
handler = FirstTimeTraceHandler()
handler.is_first_time = True
handler.collected_events = True
handler.batch_manager = bm
return handler, bm
def test_deferred_init_uses_ephemeral_and_skip_context_check(self):
"""Deferred backend init always uses ephemeral=True and skip_context_check=True."""
handler, bm = self._make_handler_with_manager()
with (
patch.object(bm, "_initialize_backend_batch") as mock_init,
patch.object(bm, "_send_events_to_backend"),
patch.object(bm, "finalize_batch"),
patch.object(bm, "_finalize_backend_batch"),
):
# trace_batch_id stays set (simulating success)
mock_init.side_effect = lambda **kwargs: None
bm.event_buffer = [MagicMock()]
handler._initialize_backend_and_send_events()
mock_init.assert_called_once()
call_kwargs = mock_init.call_args
assert call_kwargs.kwargs["use_ephemeral"] is False
assert mock_init.call_args.kwargs["use_ephemeral"] is True
assert mock_init.call_args.kwargs["skip_context_check"] is True
class TestAuthFailbackToEphemeral: