From 19f4cd66e08780c754467689b9bcb187691a279a Mon Sep 17 00:00:00 2001 From: Joao Moura Date: Fri, 26 Jun 2026 22:44:41 -0700 Subject: [PATCH] Fix deployment page link id resolution --- lib/cli/src/crewai_cli/deploy/main.py | 50 ++++++++++++++++++++++- lib/cli/tests/deploy/test_deploy_main.py | 51 ++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/lib/cli/src/crewai_cli/deploy/main.py b/lib/cli/src/crewai_cli/deploy/main.py index 085c9e1dc5..d7177bacc5 100644 --- a/lib/cli/src/crewai_cli/deploy/main.py +++ b/lib/cli/src/crewai_cli/deploy/main.py @@ -18,7 +18,7 @@ console = Console() _MISSING_LOCKFILE_ERROR_CODES = {"missing_lockfile"} _DEPLOYMENT_ID_KEYS = ("deployment_id", "deploymentId") -_DEPLOYMENT_FALLBACK_IDENTIFIER_KEYS = ("id", "uuid") +_DEPLOYMENT_FALLBACK_IDENTIFIER_KEYS = ("id",) def _run_predeploy_validation( @@ -223,7 +223,7 @@ def _open_deployment_page(self, json_response: dict[str, Any]) -> None: getattr(self.plus_api_client, "base_url", None) or DEFAULT_CREWAI_ENTERPRISE_URL ) - deployment_url = _deployment_page_url(base_url, json_response) + deployment_url = self._deployment_page_url(json_response, base_url) if not deployment_url: return @@ -239,6 +239,52 @@ def _open_deployment_page(self, json_response: dict[str, Any]) -> None: style="yellow", ) + def _deployment_page_url( + self, + json_response: dict[str, Any], + base_url: str, + ) -> str | None: + """Build the deployment show URL, resolving UUID-only responses if needed.""" + deployment_url = _deployment_page_url(base_url, json_response) + if deployment_url: + return deployment_url + + identifier = self._deployment_identifier_from_status(json_response) + if not identifier: + return None + + return ( + f"{base_url.rstrip('/')}/crewai_plus/deployments/" + f"{quote(identifier, safe='')}" + ) + + def _deployment_identifier_from_status( + self, + json_response: dict[str, Any], + ) -> str | None: + """Resolve the deployment page id from status without failing the command.""" + crew_uuid = json_response.get("uuid") + if not crew_uuid: + return None + + try: + response = self.plus_api_client.crew_status_by_uuid(str(crew_uuid)) + except Exception: + return None + + if not getattr(response, "is_success", False): + return None + + try: + status_response = response.json() + except ValueError: + return None + + if not isinstance(status_response, dict): + return None + + return _deployment_identifier(status_response) + def deploy(self, uuid: str | None = None, skip_validate: bool = False) -> None: """ Deploy a crew using either UUID or project name. diff --git a/lib/cli/tests/deploy/test_deploy_main.py b/lib/cli/tests/deploy/test_deploy_main.py index 06951f0fec..327c9f12da 100644 --- a/lib/cli/tests/deploy/test_deploy_main.py +++ b/lib/cli/tests/deploy/test_deploy_main.py @@ -187,13 +187,13 @@ def test_deployment_page_url_prefers_nested_deployment_id_over_crew_uuid(): ) -def test_deployment_page_url_falls_back_to_nested_uuid(): +def test_deployment_page_url_does_not_use_uuid_as_deployment_id(): assert ( deploy_main._deployment_page_url( "https://app.crewai.com/", - {"deployment": {"uuid": "deployment-uuid"}}, + {"uuid": "crew-uuid", "deployment": {"uuid": "deployment-uuid"}}, ) - == "https://app.crewai.com/crewai_plus/deployments/deployment-uuid" + is None ) @@ -353,6 +353,51 @@ def test_display_deployment_info_warns_when_browser_open_raises(self): "https://app.crewai.com/crewai_plus/deployments/128687" ) + def test_display_creation_success_resolves_deployment_page_id_from_status(self): + status_response = MagicMock() + status_response.is_success = True + status_response.json.return_value = { + "uuid": "new-uuid", + "id": 128774, + "status": "created", + } + self.mock_client.crew_status_by_uuid.return_value = status_response + + with patch("sys.stdout", new=StringIO()) as fake_out: + self.deploy_command._display_creation_success( + {"uuid": "new-uuid", "status": "created"} + ) + output = fake_out.getvalue() + + self.assertIn("crewai deploy push --uuid new-uuid", output) + self.assertIn( + "https://app.crewai.com/crewai_plus/deployments/128774", + output, + ) + self.assertNotIn( + "https://app.crewai.com/crewai_plus/deployments/new-uuid", + output, + ) + self.mock_client.crew_status_by_uuid.assert_called_once_with("new-uuid") + self.mock_browser_open.assert_called_once_with( + "https://app.crewai.com/crewai_plus/deployments/128774" + ) + + def test_open_deployment_page_does_not_open_uuid_url_when_status_lookup_fails( + self, + ): + status_response = MagicMock() + status_response.is_success = False + self.mock_client.crew_status_by_uuid.return_value = status_response + + with patch("sys.stdout", new=StringIO()) as fake_out: + self.deploy_command._open_deployment_page({"uuid": "new-uuid"}) + output = fake_out.getvalue() + + self.mock_client.crew_status_by_uuid.assert_called_once_with("new-uuid") + self.mock_browser_open.assert_not_called() + self.assertNotIn("deployments/new-uuid", output) + def test_display_logs(self): with patch("sys.stdout", new=StringIO()) as fake_out: self.deploy_command._display_logs(