From 2cb9f4bc350b511bfa28eca92f637f5131069486 Mon Sep 17 00:00:00 2001 From: Joao Moura Date: Mon, 15 Jun 2026 14:26:40 -0700 Subject: [PATCH] fix(cli): avoid auth retry for deploy exits --- lib/cli/src/crewai_cli/command.py | 6 +++++- lib/cli/src/crewai_cli/run_crew.py | 22 +++++++++++++++++++++- lib/cli/tests/test_crew_run_tui.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/cli/src/crewai_cli/command.py b/lib/cli/src/crewai_cli/command.py index 229c76323..d5e62cf55 100644 --- a/lib/cli/src/crewai_cli/command.py +++ b/lib/cli/src/crewai_cli/command.py @@ -13,6 +13,10 @@ from crewai_cli.plus_api import PlusAPI console = Console() +class AuthenticationRequiredError(SystemExit): + """Raised when a Plus API command needs the user to log in first.""" + + class BaseCommand: def __init__(self) -> None: self._telemetry = Telemetry() @@ -31,7 +35,7 @@ class PlusAPIMixin: style="bold red", ) console.print("Run 'crewai login' to sign up/login.", style="bold green") - raise SystemExit from None + raise AuthenticationRequiredError from None def _validate_response(self, response: httpx.Response) -> None: """Handle and display error messages from API responses. diff --git a/lib/cli/src/crewai_cli/run_crew.py b/lib/cli/src/crewai_cli/run_crew.py index 03eeb2a92..51420a3d4 100644 --- a/lib/cli/src/crewai_cli/run_crew.py +++ b/lib/cli/src/crewai_cli/run_crew.py @@ -364,20 +364,40 @@ def _chain_deploy() -> None: from rich.console import Console console = Console() + + def print_system_exit_failure(exc: SystemExit) -> None: + if isinstance(exc.code, int): + detail = f" with exit code {exc.code}" + elif exc.code: + detail = f": {exc.code}" + else: + detail = "" + console.print(f"\nDeploy failed{detail}\n", style="bold red") + try: + from crewai_cli.command import AuthenticationRequiredError from crewai_cli.deploy.main import DeployCommand console.print("\nStarting deployment…\n", style="bold #FF5A50") DeployCommand().create_crew(confirm=True, skip_validate=True) - except SystemExit: + except AuthenticationRequiredError: from crewai_cli.authentication.main import AuthenticationCommand console.print() AuthenticationCommand().login() try: DeployCommand().create_crew(confirm=True, skip_validate=True) + except AuthenticationRequiredError: + console.print( + "\nDeploy failed: authentication is still required.\n", + style="bold red", + ) + except SystemExit as e: + print_system_exit_failure(e) except Exception as e: console.print(f"\nDeploy failed: {e}\n", style="bold red") + except SystemExit as e: + print_system_exit_failure(e) except Exception as e: console.print(f"\nDeploy failed: {e}\n", style="bold red") diff --git a/lib/cli/tests/test_crew_run_tui.py b/lib/cli/tests/test_crew_run_tui.py index 891707a53..7b018107a 100644 --- a/lib/cli/tests/test_crew_run_tui.py +++ b/lib/cli/tests/test_crew_run_tui.py @@ -19,6 +19,7 @@ from crewai.events.types.tool_usage_events import ( ToolUsageFinishedEvent, ToolUsageStartedEvent, ) +from crewai_cli.command import AuthenticationRequiredError from crewai_cli import run_crew from crewai_cli.crew_run_tui import CrewRunApp @@ -68,7 +69,7 @@ def test_chain_deploy_skips_validation_after_auth_retry(monkeypatch) -> None: create_calls.append(kwargs) FakeDeployCommand.attempts += 1 if FakeDeployCommand.attempts == 1: - raise SystemExit(1) + raise AuthenticationRequiredError class FakeAuthenticationCommand: def login(self) -> None: @@ -89,6 +90,32 @@ def test_chain_deploy_skips_validation_after_auth_retry(monkeypatch) -> None: assert login_calls == [True] +def test_chain_deploy_does_not_login_for_deploy_exit(monkeypatch, capsys) -> None: + create_calls: list[dict[str, object]] = [] + login_calls: list[bool] = [] + + class FakeDeployCommand: + def create_crew(self, **kwargs) -> None: + create_calls.append(kwargs) + raise SystemExit(42) + + class FakeAuthenticationCommand: + def login(self) -> None: + login_calls.append(True) + + monkeypatch.setattr("crewai_cli.deploy.main.DeployCommand", FakeDeployCommand) + monkeypatch.setattr( + "crewai_cli.authentication.main.AuthenticationCommand", + FakeAuthenticationCommand, + ) + + run_crew._chain_deploy() + + assert create_calls == [{"confirm": True, "skip_validate": True}] + assert login_calls == [] + assert "Deploy failed with exit code 42" in capsys.readouterr().out + + def test_plan_step_status_updates_only_the_explicit_step() -> None: app = _app_with_plan()