Repository: crewAIInc/crewAI

Issue:
[BUG] Even though I turned off the telemetry feature, telemetry is still being sent.
### Description

Dear CrewAI Team,
I installed CREW with the crewai create crew research crew command. Even though I added the following ENVs, telemetry is being sent. I added the ENVs to the main.py file. 

### Steps to Reproduce

os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

### Expected behavior

There is information in the documentation that telemetry should not be sent when ENV is added.
import os
# Disable CrewAI telemetry only
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

# Disable all OpenTelemetry (including CrewAI)
os.environ['OTEL_SDK_DISABLED'] = 'true'

### Screenshots/Code snippets

import os
# Disable CrewAI telemetry only
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

# Disable all OpenTelemetry (including CrewAI)
os.environ['OTEL_SDK_DISABLED'] = 'true'


### Operating System

Ubuntu 20.04

### Python Version

3.11

### crewAI Version

0.121.1

### crewAI Tools Version

-

### Virtual Environment

Poetry

### Evidence

HTTPSConnectionPool(host='telemetry.crewai.com', port=4319): Max retries exceeded with url: /v1/traces (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7f2da7547650>, 'Connection to telemetry.crewai.com timed out. (connect timeout=30)'))

### Possible Solution

There is information in the documentation that telemetry should not be sent when ENV is added.
import os
# Disable CrewAI telemetry only
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

# Disable all OpenTelemetry (including CrewAI)
os.environ['OTEL_SDK_DISABLED'] = 'true'

### Additional context

There is information in the documentation that telemetry should not be sent when ENV is added.
import os
# Disable CrewAI telemetry only
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

# Disable all OpenTelemetry (including CrewAI)
os.environ['OTEL_SDK_DISABLED'] = 'true'

Hints from issue comments:
@lucasgomide: hey @fzozyurt 
would you mind sharing how your are define that? I ran the following snippet code.. everything works good

```python
import os
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

from crewai import LLM

llm = LLM(model="o3-mini", temperature=0.5)

response = llm.call(
    messages=[
        {"role": "user", "content": "What is the weather in San Francisco?"},
    ],
)

print(response)

```@fzozyurt: > hey [@fzozyurt](https://github.com/fzozyurt) would you mind sharing how your are define that? I ran the following snippet code.. everything works good
> 
> import os
> os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'
> 
> from crewai import LLM
> 
> llm = LLM(model="o3-mini", temperature=0.5)
> 
> response = llm.call(
>     messages=[
>         {"role": "user", "content": "What is the weather in San Francisco?"},
>     ],
> )
> 
> print(response)

Dear @lucasgomide ,
I built Flow with CrewAI CLI. I defined this ENV in the main.py file in the FLOW I created. However, it does not affect the Agents in Flow and Telemetry is tried to be transmitted.

My main.py example file

```
#!/usr/bin/env python
from random import randint
from pydantic import BaseModel
from crewai.flow import Flow, listen, start
from asaitk.crews.poem_crew.poem_crew import PoemCrew


class PoemState(BaseModel):
    sentence_count: int = 1
    poem: str = ""

import os
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

class PoemFlow(Flow[PoemState]):

    @start()
    def generate_sentence_count(self):
        print("Generating sentence count")
        self.state.sentence_count = randint(1, 5)

    @listen(generate_sentence_count)
    def generate_poem(self):
        print("Generating poem")
        result = (
            PoemCrew()
            .crew()
            .kickoff(inputs={"sentence_count": self.state.sentence_count})
        )

        print("Poem generated", result.raw)
        self.state.poem = result.raw

    @listen(generate_poem)
    def save_poem(self):
        print("Saving poem")
        with open("poem.txt", "w") as f:
            f.write(self.state.poem)


def kickoff():
    poem_flow = PoemFlow()
    poem_flow.kickoff()


def plot():
    poem_flow = PoemFlow()
    poem_flow.plot()


if __name__ == "__main__":
    kickoff()

```

@yasserkhalil93: I am facing the same issue too:



  The last sentence in the task keeps repeating:

# Agent: Content Planner
## Task: 1. Prioritize the latest trends, key players, and noteworthy news on Artificial Intelligence.
2. Identify the target audience, considering their interests and pain points.
3. Develop a detailed content outline including an introduction, key points, and a call to action.
4. Include SEO keywords and relevant data or sources.[00m
4. Include SEO keywords and relevant data or sources.[00m
4. Include SEO keywords and relevant data or sources.[00m
4. Include SEO keywords and relevant data or sources.[00m
4. Include SEO keywords and relevant data or sources.[00m
4. Include SEO keywords and relevant data or sources.[00m

until I get the following error:

RecursionError: maximum recursion depth exceeded

HTTPSConnectionPool(host='telemetry.crewai.com', port=4319): Max retries exceeded with url: /v1/traces (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1010)')))@Vidit-Ostwal: > I am facing the same issue too:
> 
> The last sentence in the task keeps repeating:
> 
> # Agent: Content Planner
> ## Task: 1. Prioritize the latest trends, key players, and noteworthy news on Artificial Intelligence.
> 2. Identify the target audience, considering their interests and pain points.
> 3. Develop a detailed content outline including an introduction, key points, and a call to action.
> 4. Include SEO keywords and relevant data or sources.�[00m
> 5. Include SEO keywords and relevant data or sources.�[00m
> 6. Include SEO keywords and relevant data or sources.�[00m
> 7. Include SEO keywords and relevant data or sources.�[00m
> 8. Include SEO keywords and relevant data or sources.�[00m
> 9. Include SEO keywords and relevant data or sources.�[00m
> 
> until I get the following error:
> 
> RecursionError: maximum recursion depth exceeded
> 
> HTTPSConnectionPool(host='telemetry.crewai.com', port=4319): Max retries exceeded with url: /v1/traces (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1010)')))

Can you upgrade your crewai version, or use .py files for inference?@yasserkhalil93: my current version is: 0.121.1

I am using Jupiter Notebook, code is as follows:

```
from crewai import Agent, Task, Crew
import os
from utils import get_openai_api_key

os.environ["OPENAI_API_KEY"] = get_openai_api_key()
os.environ["OPENAI_MODEL_NAME"] = 'gpt-3.5-turbo-0125'
os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'

planner = Agent(
    role="Content Planner",
    goal="Plan engaging and factually accurate content on {topic}",
    backstory="You're working on planning a blog article "
              "about the topic: {topic}."
              "You collect information that helps the "
              "audience learn something "
              "and make informed decisions. "
              "Your work is the basis for "
              "the Content Writer to write an article on this topic.",
    max_iter= 2,
    allow_delegation=False,
	verbose=True
)

writer = Agent(
    role="Content Writer",
    goal="Write insightful and factually accurate "
         "opinion piece about the topic: {topic}",
    backstory="You're working on a writing "
              "a new opinion piece about the topic: {topic}. "
              "You base your writing on the work of "
              "the Content Planner, who provides an outline "
              "and relevant context about the topic. "
              "You follow the main objectives and "
              "direction of the outline, "
              "as provide by the Content Planner. "
              "You also provide objective and impartial insights "
              "and back them up with information "
              "provide by the Content Planner. "
              "You acknowledge in your opinion piece "
              "when your statements are opinions "
              "as opposed to objective statements.",
    # llm=llm,
    max_iter= 2,
    
    allow_delegation=False,
    verbose=True
)

editor = Agent(
    role="Editor",
    goal="Edit a given blog post to align with "
         "the writing style of the organization. ",
    backstory="You are an editor who receives a blog post "
              "from the Content Writer. "
              "Your goal is to review the blog post "
              "to ensure that it follows journalistic best practices,"
              "provides balanced viewpoints "
              "when providing opinions or assertions, "
              "and also avoids major controversial topics "
              "or opinions when possible.",
    # llm=llm,
    max_iter= 2,
    allow_delegation=False,
    verbose=True
)

plan = Task(
    description=(
        "1. Prioritize the latest trends, key players, "
            "and noteworthy news on {topic}.\n"
        "2. Identify the target audience, considering "
            "their interests and pain points.\n"
        "3. Develop a detailed content outline including "
            "an introduction, key points, and a call to action.\n"
        "4. Include SEO keywords and relevant data or sources."
    ),
    expected_output="A comprehensive content plan document "
        "with an outline, audience analysis, "
        "SEO keywords, and resources.",
    agent=planner,
)


write = Task(
    description=(
        "1. Use the content plan to craft a compelling "
            "blog post on {topic}.\n"
        "2. Incorporate SEO keywords naturally.\n"
		"3. Sections/Subtitles are properly named "
            "in an engaging manner.\n"
        "4. Ensure the post is structured with an "
            "engaging introduction, insightful body, "
            "and a summarizing conclusion.\n"
        "5. Proofread for grammatical errors and "
            "alignment with the brand's voice.\n"
    ),
    expected_output="A well-written blog post "
        "in markdown format, ready for publication, "
        "each section should have 2 or 3 paragraphs.",
    agent=writer,
)


edit = Task(
    description=("Proofread the given blog post for "
                 "grammatical errors and "
                 "alignment with the brand's voice."),
    expected_output="A well-written blog post in markdown format, "
                    "ready for publication, "
                    "each section should have 2 or 3 paragraphs.",
    agent=editor
)


crew = Crew(
    agents=[planner, writer, editor],
    tasks=[plan, write, edit],
    verbose=True,
)


result = crew.kickoff(inputs={"topic": "Artificial Intelligence"})

```@Vidit-Ostwal: > my current version is: 0.121.1
> 
> I am using Jupiter Notebook, code is as follows:
> 
> ```
> from crewai import Agent, Task, Crew
> import os
> from utils import get_openai_api_key
> 
> os.environ["OPENAI_API_KEY"] = get_openai_api_key()
> os.environ["OPENAI_MODEL_NAME"] = 'gpt-3.5-turbo-0125'
> os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'
> 
> planner = Agent(
>     role="Content Planner",
>     goal="Plan engaging and factually accurate content on {topic}",
>     backstory="You're working on planning a blog article "
>               "about the topic: {topic}."
>               "You collect information that helps the "
>               "audience learn something "
>               "and make informed decisions. "
>               "Your work is the basis for "
>               "the Content Writer to write an article on this topic.",
>     max_iter= 2,
>     allow_delegation=False,
> 	verbose=True
> )
> 
> writer = Agent(
>     role="Content Writer",
>     goal="Write insightful and factually accurate "
>          "opinion piece about the topic: {topic}",
>     backstory="You're working on a writing "
>               "a new opinion piece about the topic: {topic}. "
>               "You base your writing on the work of "
>               "the Content Planner, who provides an outline "
>               "and relevant context about the topic. "
>               "You follow the main objectives and "
>               "direction of the outline, "
>               "as provide by the Content Planner. "
>               "You also provide objective and impartial insights "
>               "and back them up with information "
>               "provide by the Content Planner. "
>               "You acknowledge in your opinion piece "
>               "when your statements are opinions "
>               "as opposed to objective statements.",
>     # llm=llm,
>     max_iter= 2,
>     
>     allow_delegation=False,
>     verbose=True
> )
> 
> editor = Agent(
>     role="Editor",
>     goal="Edit a given blog post to align with "
>          "the writing style of the organization. ",
>     backstory="You are an editor who receives a blog post "
>               "from the Content Writer. "
>               "Your goal is to review the blog post "
>               "to ensure that it follows journalistic best practices,"
>               "provides balanced viewpoints "
>               "when providing opinions or assertions, "
>               "and also avoids major controversial topics "
>               "or opinions when possible.",
>     # llm=llm,
>     max_iter= 2,
>     allow_delegation=False,
>     verbose=True
> )
> 
> plan = Task(
>     description=(
>         "1. Prioritize the latest trends, key players, "
>             "and noteworthy news on {topic}.\n"
>         "2. Identify the target audience, considering "
>             "their interests and pain points.\n"
>         "3. Develop a detailed content outline including "
>             "an introduction, key points, and a call to action.\n"
>         "4. Include SEO keywords and relevant data or sources."
>     ),
>     expected_output="A comprehensive content plan document "
>         "with an outline, audience analysis, "
>         "SEO keywords, and resources.",
>     agent=planner,
> )
> 
> 
> write = Task(
>     description=(
>         "1. Use the content plan to craft a compelling "
>             "blog post on {topic}.\n"
>         "2. Incorporate SEO keywords naturally.\n"
> 		"3. Sections/Subtitles are properly named "
>             "in an engaging manner.\n"
>         "4. Ensure the post is structured with an "
>             "engaging introduction, insightful body, "
>             "and a summarizing conclusion.\n"
>         "5. Proofread for grammatical errors and "
>             "alignment with the brand's voice.\n"
>     ),
>     expected_output="A well-written blog post "
>         "in markdown format, ready for publication, "
>         "each section should have 2 or 3 paragraphs.",
>     agent=writer,
> )
> 
> 
> edit = Task(
>     description=("Proofread the given blog post for "
>                  "grammatical errors and "
>                  "alignment with the brand's voice."),
>     expected_output="A well-written blog post in markdown format, "
>                     "ready for publication, "
>                     "each section should have 2 or 3 paragraphs.",
>     agent=editor
> )
> 
> 
> crew = Crew(
>     agents=[planner, writer, editor],
>     tasks=[plan, write, edit],
>     verbose=True,
> )
> 
> 
> result = crew.kickoff(inputs={"topic": "Artificial Intelligence"})
> 
> ```
Can you try using .py files for inference?
@yasserkhalil93: running it in .py file resolved the issue@lucasgomide: Personally, I'm used to using proper environment loading tools to set env vars, such as direnv or loadenv. Alternatively, I set them inline when running a command, like: MY_VAR=true crewai run

Solution patch:
diff --git a/src/crewai/telemetry/telemetry.py b/src/crewai/telemetry/telemetry.py
index ffd78d28..f75809a0 100644
--- a/src/crewai/telemetry/telemetry.py
+++ b/src/crewai/telemetry/telemetry.py
@@ -8,7 +8,7 @@ import platform
 import warnings
 from contextlib import contextmanager
 from importlib.metadata import version
-from typing import TYPE_CHECKING, Any, Optional
+from typing import TYPE_CHECKING, Any, Callable, Optional
 import threading
 
 from opentelemetry import trace
@@ -73,11 +73,16 @@ class Telemetry:
             with cls._lock:
                 if cls._instance is None:
                     cls._instance = super(Telemetry, cls).__new__(cls)
+                    cls._instance._initialized = False
         return cls._instance
 
     def __init__(self) -> None:
+        if hasattr(self, '_initialized') and self._initialized:
+            return
+        
         self.ready: bool = False
         self.trace_set: bool = False
+        self._initialized: bool = True
 
         if self._is_telemetry_disabled():
             return
@@ -113,6 +118,10 @@ class Telemetry:
             or os.getenv("CREWAI_DISABLE_TELEMETRY", "false").lower() == "true"
         )
 
+    def _should_execute_telemetry(self) -> bool:
+        """Check if telemetry operations should be executed."""
+        return self.ready and not self._is_telemetry_disabled()
+
     def set_tracer(self):
         if self.ready and not self.trace_set:
             try:
@@ -123,8 +132,9 @@ class Telemetry:
                 self.ready = False
                 self.trace_set = False
 
-    def _safe_telemetry_operation(self, operation):
-        if not self.ready:
+    def _safe_telemetry_operation(self, operation: Callable[[], None]) -> None:
+        """Execute telemetry operation safely, checking both readiness and environment variables."""
+        if not self._should_execute_telemetry():
             return
         try:
             operation()
@@ -423,7 +433,8 @@ class Telemetry:
 
             return span
 
-        return self._safe_telemetry_operation(operation)
+        self._safe_telemetry_operation(operation)
+        return None
 
     def task_ended(self, span: Span, task: Task, crew: Crew):
         """Records the completion of a task execution in a crew.
@@ -773,7 +784,8 @@ class Telemetry:
             return span
 
         if crew.share_crew:
-            return self._safe_telemetry_operation(operation)
+            self._safe_telemetry_operation(operation)
+            return operation()
         return None
 
     def end_crew(self, crew, final_string_output):


Test patch:
diff --git a/tests/telemetry/test_telemetry.py b/tests/telemetry/test_telemetry.py
index 51c5a79f..27757832 100644
--- a/tests/telemetry/test_telemetry.py
+++ b/tests/telemetry/test_telemetry.py
@@ -9,6 +9,14 @@ from crewai.telemetry import Telemetry
 from opentelemetry import trace
 
 
+@pytest.fixture(autouse=True)
+def cleanup_telemetry():
+    """Automatically clean up Telemetry singleton between tests."""
+    Telemetry._instance = None
+    yield
+    Telemetry._instance = None
+
+
 @pytest.mark.parametrize(
     "env_var,value,expected_ready",
     [
diff --git a/tests/telemetry/test_telemetry_disable.py b/tests/telemetry/test_telemetry_disable.py
index 16c02aca..96738ad5 100644
--- a/tests/telemetry/test_telemetry_disable.py
+++ b/tests/telemetry/test_telemetry_disable.py
@@ -1,11 +1,19 @@
 import os
-from unittest.mock import patch
+from unittest.mock import patch, MagicMock
 
 import pytest
 
 from crewai.telemetry import Telemetry
 
 
+@pytest.fixture(autouse=True)
+def cleanup_telemetry():
+    """Automatically clean up Telemetry singleton between tests."""
+    Telemetry._instance = None
+    yield
+    Telemetry._instance = None
+
+
 @pytest.mark.parametrize("env_var,value,expected_ready", [
     ("OTEL_SDK_DISABLED", "true", False),
     ("OTEL_SDK_DISABLED", "TRUE", False),
@@ -28,3 +36,59 @@ def test_telemetry_enabled_by_default():
         with patch("crewai.telemetry.telemetry.TracerProvider"):
             telemetry = Telemetry()
             assert telemetry.ready is True
+
+
+def test_telemetry_disable_after_singleton_creation():
+    """Test that telemetry operations are disabled when env var is set after singleton creation."""
+    with patch.dict(os.environ, {}, clear=True):
+        with patch("crewai.telemetry.telemetry.TracerProvider"):
+            telemetry = Telemetry()
+            assert telemetry.ready is True
+            
+            mock_operation = MagicMock()
+            telemetry._safe_telemetry_operation(mock_operation)
+            mock_operation.assert_called_once()
+            
+            mock_operation.reset_mock()
+            
+            os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'
+            
+            telemetry._safe_telemetry_operation(mock_operation)
+            mock_operation.assert_not_called()
+
+
+def test_telemetry_disable_with_multiple_instances():
+    """Test that multiple telemetry instances respect dynamically changed env vars."""
+    with patch.dict(os.environ, {}, clear=True):
+        with patch("crewai.telemetry.telemetry.TracerProvider"):
+            telemetry1 = Telemetry()
+            assert telemetry1.ready is True
+            
+            os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true'
+            
+            telemetry2 = Telemetry()
+            assert telemetry2 is telemetry1
+            assert telemetry2.ready is True
+            
+            mock_operation = MagicMock()
+            telemetry2._safe_telemetry_operation(mock_operation)
+            mock_operation.assert_not_called()
+
+
+def test_telemetry_otel_sdk_disabled_after_creation():
+    """Test that OTEL_SDK_DISABLED also works when set after singleton creation."""
+    with patch.dict(os.environ, {}, clear=True):
+        with patch("crewai.telemetry.telemetry.TracerProvider"):
+            telemetry = Telemetry()
+            assert telemetry.ready is True
+            
+            mock_operation = MagicMock()
+            telemetry._safe_telemetry_operation(mock_operation)
+            mock_operation.assert_called_once()
+            
+            mock_operation.reset_mock()
+            
+            os.environ['OTEL_SDK_DISABLED'] = 'true'
+            
+            telemetry._safe_telemetry_operation(mock_operation)
+            mock_operation.assert_not_called()