From 2ebb2e845fa1abb8538e196803e1d5206211cbb0 Mon Sep 17 00:00:00 2001 From: Heitor Carvalho Date: Mon, 13 Oct 2025 12:42:03 -0300 Subject: [PATCH 1/4] fix: add a leeway of 10s when decoding jwt (#3698) --- src/crewai/cli/authentication/utils.py | 1 + tests/cli/authentication/test_utils.py | 33 ++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/crewai/cli/authentication/utils.py b/src/crewai/cli/authentication/utils.py index 849dda594..08955092b 100644 --- a/src/crewai/cli/authentication/utils.py +++ b/src/crewai/cli/authentication/utils.py @@ -30,6 +30,7 @@ def validate_jwt_token( algorithms=["RS256"], audience=audience, issuer=issuer, + leeway=10.0, options={ "verify_signature": True, "verify_exp": True, diff --git a/tests/cli/authentication/test_utils.py b/tests/cli/authentication/test_utils.py index 860ec7aae..5df00db18 100644 --- a/tests/cli/authentication/test_utils.py +++ b/tests/cli/authentication/test_utils.py @@ -1,7 +1,7 @@ -import jwt import unittest from unittest.mock import MagicMock, patch +import jwt from crewai.cli.authentication.utils import validate_jwt_token @@ -17,19 +17,22 @@ class TestUtils(unittest.TestCase): key="mock_signing_key" ) + jwt_token = "aaaaa.bbbbbb.cccccc" # noqa: S105 + decoded_token = validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token=jwt_token, jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", ) mock_jwt.decode.assert_called_with( - "aaaaa.bbbbbb.cccccc", + jwt_token, "mock_signing_key", algorithms=["RS256"], audience="app_id_xxxx", issuer="https://mock_issuer", + leeway=10.0, options={ "verify_signature": True, "verify_exp": True, @@ -43,9 +46,9 @@ class TestUtils(unittest.TestCase): def test_validate_jwt_token_expired(self, mock_jwt, mock_pyjwkclient): mock_jwt.decode.side_effect = jwt.ExpiredSignatureError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", @@ -53,9 +56,9 @@ class TestUtils(unittest.TestCase): def test_validate_jwt_token_invalid_audience(self, mock_jwt, mock_pyjwkclient): mock_jwt.decode.side_effect = jwt.InvalidAudienceError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", @@ -63,9 +66,9 @@ class TestUtils(unittest.TestCase): def test_validate_jwt_token_invalid_issuer(self, mock_jwt, mock_pyjwkclient): mock_jwt.decode.side_effect = jwt.InvalidIssuerError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", @@ -75,9 +78,9 @@ class TestUtils(unittest.TestCase): self, mock_jwt, mock_pyjwkclient ): mock_jwt.decode.side_effect = jwt.MissingRequiredClaimError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", @@ -85,9 +88,9 @@ class TestUtils(unittest.TestCase): def test_validate_jwt_token_jwks_error(self, mock_jwt, mock_pyjwkclient): mock_jwt.decode.side_effect = jwt.exceptions.PyJWKClientError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", @@ -95,9 +98,9 @@ class TestUtils(unittest.TestCase): def test_validate_jwt_token_invalid_token(self, mock_jwt, mock_pyjwkclient): mock_jwt.decode.side_effect = jwt.InvalidTokenError - with self.assertRaises(Exception): + with self.assertRaises(Exception): # noqa: B017 validate_jwt_token( - jwt_token="aaaaa.bbbbbb.cccccc", + jwt_token="aaaaa.bbbbbb.cccccc", # noqa: S106 jwks_url="https://mock_jwks_url", issuer="https://mock_issuer", audience="app_id_xxxx", From 814c9621968c18977adc349fa1e745f412d7f227 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:46:22 -0700 Subject: [PATCH 2/4] chore: update crewAI version to 0.203.1 in multiple templates (#3699) - Bumped the `crewai` version in `__init__.py` to 0.203.1. - Updated the dependency versions in the crew, flow, and tool templates' `pyproject.toml` files to reflect the new `crewai` version. --- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index ffc7aa844..9f44bc054 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -40,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None: _suppress_pydantic_deprecation_warnings() -__version__ = "0.203.0" +__version__ = "0.203.1" _telemetry_submitted = False diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 0f26e0a24..b34088f16 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<3.14" dependencies = [ - "crewai[tools]>=0.203.0,<1.0.0" + "crewai[tools]>=0.203.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 75e0240f7..e3c0c816e 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<3.14" dependencies = [ - "crewai[tools]>=0.203.0,<1.0.0", + "crewai[tools]>=0.203.1,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 70d0cf5f9..61d4343b9 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<3.14" dependencies = [ - "crewai[tools]>=0.203.0" + "crewai[tools]>=0.203.1" ] [tool.crewai] From bf2e2a42da0b0563064fcf5d3eafdea50483483f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 13 Oct 2025 19:36:19 -0700 Subject: [PATCH 3/4] fix: don't error out if there it no input() available - Specific to jupyter notebooks --- src/crewai/events/listeners/tracing/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/crewai/events/listeners/tracing/utils.py b/src/crewai/events/listeners/tracing/utils.py index 7b1978912..03089994c 100644 --- a/src/crewai/events/listeners/tracing/utils.py +++ b/src/crewai/events/listeners/tracing/utils.py @@ -358,7 +358,8 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool: try: response = input().strip().lower() result[0] = response in ["y", "yes"] - except (EOFError, KeyboardInterrupt): + except (EOFError, KeyboardInterrupt, OSError, LookupError): + # Handle all input-related errors silently result[0] = False input_thread = threading.Thread(target=get_input, daemon=True) @@ -371,6 +372,7 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool: return result[0] except Exception: + # Suppress any warnings or errors and assume "no" return False From f0fb349ddfa459ff35116daf2e56f779d67eb93b Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:22:39 +0530 Subject: [PATCH 4/4] Fixing copy and adding NOT_SPECIFIED check in task.py (#3690) * Fixing copy and adding NOT_SPECIFIED check: * Fixed mypy issues * Added test Cases * added linting checks * Removed the docs bot folder * Fixed ruff checks * Remove secret_folder from tracking --------- Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> --- src/crewai/task.py | 8 +++++--- tests/test_task.py | 47 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/crewai/task.py b/src/crewai/task.py index ebf284317..5fa9149f9 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -7,7 +7,7 @@ import uuid import warnings from collections.abc import Callable from concurrent.futures import Future -from copy import copy +from copy import copy as shallow_copy from hashlib import md5 from pathlib import Path from typing import ( @@ -672,7 +672,9 @@ Follow these guidelines: copied_data = {k: v for k, v in copied_data.items() if v is not None} cloned_context = ( - [task_mapping[context_task.key] for context_task in self.context] + self.context + if self.context is NOT_SPECIFIED + else [task_mapping[context_task.key] for context_task in self.context] if isinstance(self.context, list) else None ) @@ -681,7 +683,7 @@ Follow these guidelines: return next((agent for agent in agents if agent.role == role), None) cloned_agent = get_agent_by_role(self.agent.role) if self.agent else None - cloned_tools = copy(self.tools) if self.tools else [] + cloned_tools = shallow_copy(self.tools) if self.tools else [] return self.__class__( **copied_data, diff --git a/tests/test_task.py b/tests/test_task.py index 0e304df54..169088c83 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -1218,7 +1218,7 @@ def test_create_directory_false(): assert not resolved_dir.exists() with pytest.raises( - RuntimeError, match="Directory .* does not exist and create_directory is False" + RuntimeError, match=r"Directory .* does not exist and create_directory is False" ): task._save_file("test content") @@ -1635,3 +1635,48 @@ def test_task_interpolation_with_hyphens(): assert "say hello world" in task.prompt() assert result.raw == "Hello, World!" + + +def test_task_copy_with_none_context(): + original_task = Task( + description="Test task", + expected_output="Test output", + context=None + ) + + new_task = original_task.copy(agents=[], task_mapping={}) + assert original_task.context is None + assert new_task.context is None + + +def test_task_copy_with_not_specified_context(): + from crewai.utilities.constants import NOT_SPECIFIED + original_task = Task( + description="Test task", + expected_output="Test output", + ) + + new_task = original_task.copy(agents=[], task_mapping={}) + assert original_task.context is NOT_SPECIFIED + assert new_task.context is NOT_SPECIFIED + + +def test_task_copy_with_list_context(): + """Test that copying a task with list context works correctly.""" + task1 = Task( + description="Task 1", + expected_output="Output 1" + ) + task2 = Task( + description="Task 2", + expected_output="Output 2", + context=[task1] + ) + + task_mapping = {task1.key: task1} + + copied_task2 = task2.copy(agents=[], task_mapping=task_mapping) + + assert isinstance(copied_task2.context, list) + assert len(copied_task2.context) == 1 + assert copied_task2.context[0] is task1