Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/crewai/src/crewai/telemetry/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ def _register_shutdown_handlers(self) -> None:

self._original_handlers: dict[int, Any] = {}

if threading.current_thread() is not threading.main_thread():
logger.debug(
"CrewAI telemetry: Skipping signal handler registration "
"(not running in main thread)."
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest enhancing the log message to be more actionable for users. The current message doesn't tell them how to disable the warning or where to learn more.

Suggested change:

logger.debug(
            "CrewAI telemetry: Skipping signal handler registration (not running in main thread). "
            "To disable this warning or disable crewai telemetry entirely, set CREWAI_DISABLE_TELEMETRY=true. "
            "See: https://docs.crewai.com/telemetry"
        )

return

self._register_signal_handler(signal.SIGTERM, SigTermEvent, shutdown=True)
self._register_signal_handler(signal.SIGINT, SigIntEvent, shutdown=True)
if hasattr(signal, "SIGHUP"):
Expand Down
89 changes: 88 additions & 1 deletion lib/crewai/tests/telemetry/test_telemetry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import threading
from unittest.mock import patch
from unittest.mock import MagicMock, patch

import pytest
from crewai import Agent, Crew, Task
Expand Down Expand Up @@ -121,3 +121,90 @@ def create_instance():
thread.join()

assert all(instance is telemetry1 for instance in instances)


def test_signal_handler_registration_skipped_in_non_main_thread():
"""Test that signal handler registration is skipped when running from a non-main thread.

This test verifies that when Telemetry is initialized from a non-main thread,
the signal handler registration is skipped without raising noisy ValueError tracebacks.
See: https://github.com/crewAIInc/crewAI/issues/4289
"""
Telemetry._instance = None

result = {"register_signal_handler_called": False, "error": None}

def init_telemetry_in_thread():
try:
with patch("crewai.telemetry.telemetry.TracerProvider"):
with patch.object(
Telemetry,
"_register_signal_handler",
wraps=lambda *args, **kwargs: None,
) as mock_register:
telemetry = Telemetry()
result["register_signal_handler_called"] = mock_register.called
result["telemetry"] = telemetry
except Exception as e:
result["error"] = e

thread = threading.Thread(target=init_telemetry_in_thread)
thread.start()
thread.join()

assert result["error"] is None, f"Unexpected error: {result['error']}"
assert (
result["register_signal_handler_called"] is False
), "Signal handler should not be registered in non-main thread"


def test_signal_handler_registration_skipped_logs_debug_message():
"""Test that a debug message is logged when signal handler registration is skipped.

This test verifies that when Telemetry is initialized from a non-main thread,
a debug message is logged indicating that signal handler registration was skipped.
"""
Telemetry._instance = None

result = {"telemetry": None, "error": None, "debug_calls": []}

mock_logger_debug = MagicMock()

def init_telemetry_in_thread():
try:
with patch("crewai.telemetry.telemetry.TracerProvider"):
with patch(
"crewai.telemetry.telemetry.logger.debug", mock_logger_debug
):
result["telemetry"] = Telemetry()
result["debug_calls"] = [
str(call) for call in mock_logger_debug.call_args_list
]
except Exception as e:
result["error"] = e

thread = threading.Thread(target=init_telemetry_in_thread)
thread.start()
thread.join()

assert result["error"] is None, f"Unexpected error: {result['error']}"
assert result["telemetry"] is not None

debug_calls = result["debug_calls"]
assert any(
"Skipping signal handler registration" in call for call in debug_calls
), f"Expected debug message about skipping signal handler registration, got: {debug_calls}"


def test_signal_handlers_registered_in_main_thread():
"""Test that signal handlers are registered when running from the main thread."""
Telemetry._instance = None

with patch("crewai.telemetry.telemetry.TracerProvider"):
with patch(
"crewai.telemetry.telemetry.Telemetry._register_signal_handler"
) as mock_register:
telemetry = Telemetry()

assert telemetry.ready is True
assert mock_register.call_count >= 2
Loading