From b8c89a81f6941c9e70a1ea20802ea1f769194502 Mon Sep 17 00:00:00 2001 From: Joao Moura Date: Mon, 15 Jun 2026 10:32:45 -0700 Subject: [PATCH] fix(cli): skip json crew sync when locked --- lib/cli/src/crewai_cli/run_crew.py | 15 ++++++++---- lib/cli/tests/test_run_crew.py | 37 ++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/lib/cli/src/crewai_cli/run_crew.py b/lib/cli/src/crewai_cli/run_crew.py index ab4a86e84..4f4e60a3b 100644 --- a/lib/cli/src/crewai_cli/run_crew.py +++ b/lib/cli/src/crewai_cli/run_crew.py @@ -256,9 +256,16 @@ def _run_json_crew(trained_agents_file: str | None = None) -> Any: return app._crew_result -def _install_json_crew_dependencies() -> None: - """Lock and sync JSON crew projects before loading them in-process.""" - if not (Path.cwd() / "pyproject.toml").is_file(): +def _has_lockfile(project_root: Path | None = None) -> bool: + """Return True when the project already has a dependency lockfile.""" + root = project_root or Path.cwd() + return (root / "uv.lock").is_file() or (root / "poetry.lock").is_file() + + +def _install_json_crew_dependencies_if_needed() -> None: + """Lock and sync JSON crew projects only when no lockfile exists.""" + project_root = Path.cwd() + if not (project_root / "pyproject.toml").is_file() or _has_lockfile(project_root): return from crewai_cli.install_crew import install_crew @@ -286,7 +293,7 @@ def _run_json_crew_in_project_env(trained_agents_file: str | None = None) -> Any if not (Path.cwd() / "pyproject.toml").is_file(): return _run_json_crew(trained_agents_file=trained_agents_file) - _install_json_crew_dependencies() + _install_json_crew_dependencies_if_needed() command = ["uv", "run", "--no-sync", "python", "-c", _JSON_CREW_RUNNER_CODE] env = build_env_with_all_tool_credentials() diff --git a/lib/cli/tests/test_run_crew.py b/lib/cli/tests/test_run_crew.py index cdb8218be..945836a19 100644 --- a/lib/cli/tests/test_run_crew.py +++ b/lib/cli/tests/test_run_crew.py @@ -39,7 +39,7 @@ def test_json_run_uses_project_env_when_pyproject_exists(monkeypatch, tmp_path: monkeypatch.setattr( run_crew_module, - "_install_json_crew_dependencies", + "_install_json_crew_dependencies_if_needed", lambda: install_calls.append(True), ) monkeypatch.setattr( @@ -161,7 +161,9 @@ def test_json_project_env_run_failure_exits_nonzero(monkeypatch, tmp_path: Path) monkeypatch.chdir(tmp_path) (tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n") - monkeypatch.setattr(run_crew_module, "_install_json_crew_dependencies", lambda: None) + monkeypatch.setattr( + run_crew_module, "_install_json_crew_dependencies_if_needed", lambda: None + ) monkeypatch.setattr( run_crew_module, "build_env_with_all_tool_credentials", lambda: {} ) @@ -177,10 +179,10 @@ def test_json_project_env_run_failure_exits_nonzero(monkeypatch, tmp_path: Path) assert exc_info.value.code == 7 -def test_json_run_installs_dependencies_when_pyproject_exists( +def test_json_run_installs_dependencies_when_pyproject_has_no_lockfile( monkeypatch, tmp_path: Path ): - """JSON crew runs should lock/sync project dependencies before loading.""" + """JSON crew runs should lock/sync project dependencies only once.""" monkeypatch.chdir(tmp_path) (tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n") calls = [] @@ -192,11 +194,32 @@ def test_json_run_installs_dependencies_when_pyproject_exists( monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) - run_crew_module._install_json_crew_dependencies() + run_crew_module._install_json_crew_dependencies_if_needed() assert calls == [([], True, False)] +@pytest.mark.parametrize("lockfile", ["uv.lock", "poetry.lock"]) +def test_json_run_skips_dependency_install_when_lockfile_exists( + monkeypatch, tmp_path: Path, lockfile: str +): + monkeypatch.chdir(tmp_path) + (tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n") + (tmp_path / lockfile).write_text("# lock\n") + calls = [] + + def fake_install_crew( + proxy_options, *, raise_on_error=False, install_project=None + ): + calls.append((proxy_options, raise_on_error, install_project)) + + monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) + + run_crew_module._install_json_crew_dependencies_if_needed() + + assert calls == [] + + def test_json_run_skips_dependency_install_without_pyproject( monkeypatch, tmp_path: Path ): @@ -210,7 +233,7 @@ def test_json_run_skips_dependency_install_without_pyproject( monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) - run_crew_module._install_json_crew_dependencies() + run_crew_module._install_json_crew_dependencies_if_needed() assert calls == [] @@ -227,7 +250,7 @@ def test_json_run_install_failure_exits_nonzero(monkeypatch, tmp_path: Path): monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) with pytest.raises(SystemExit) as exc_info: - run_crew_module._install_json_crew_dependencies() + run_crew_module._install_json_crew_dependencies_if_needed() assert exc_info.value.code == 42