fix(cli): pin AMP's proxy and CA in the JSON-crew runner too

`crewai run` runs a JSON crew in a fresh `uv run python -c` interpreter that
never imports cli.py, and `_run_json_crew` loads the project's .env with
override=True before tracing first imports crewai_core.plus_api, so the pin
recorded the project's proxy and CA. The runner now imports plus_api before
anything else. Declarative flows re-run through `uv run crewai run`, which
already pins in cli.py.

Also keeps `_requests_sent_by` off machine and OS proxies, so a developer's
proxy can't mount a real transport ahead of its MockTransport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Joao MouraandClaude Opus 5.5 committed 2026-10-18 22:50:14 -07:00
1 parent b813e5bb25
commit 313df2b229
3 files changed
+62 -3

No files matched your search

+8
View File
@@ -56,6 +56,14 @@ source_dir = os.environ.get("CREWAI_RUNNER_SOURCE_DIR")
if source_dir:
sys.path.insert(0, source_dir)
# This interpreter never imports cli.py, so pin the machine's proxy and CA for
# AMP requests here, before _run_json_crew loads the project's .env. A
# crewai-core too old to have plus_api pins nothing and ignores proxies anyway.
try:
import crewai_core.plus_api
except ImportError:
pass
package_dir = Path(os.environ["CREWAI_CLI_RUNNER_PACKAGE_DIR"])
package_spec = importlib.util.spec_from_file_location(
"crewai_cli",
+8 -3
View File
@@ -551,9 +551,14 @@ def _requests_sent_by(call: Callable[[PlusAPI], object]) -> list[httpx.Request]:
return httpx.Response(200, json={})
transport = httpx.MockTransport(answer)
with patch(
"crewai_core.plus_api.httpx.Client",
side_effect=lambda **kwargs: _RealHttpxClient(transport=transport, **kwargs),
# A machine or OS proxy would mount a real transport ahead of the mock one.
with (
patch.dict("crewai_core.plus_api._MACHINE_TRANSPORT_ENV", clear=True),
patch("crewai_core.plus_api._os_proxies", dict),
patch(
"crewai_core.plus_api.httpx.Client",
side_effect=lambda **kwargs: _RealHttpxClient(transport=transport, **kwargs),
),
):
call(PlusAPI("test_api_key"))
return sent
+46
View File
@@ -1,5 +1,6 @@
"""Tests for crewai_cli.run_crew JSON crew handling."""
import json
import os
from pathlib import Path
import subprocess
@@ -232,6 +233,51 @@ def test_json_runner_code_loads_current_cli_package_over_project_env(tmp_path: P
assert marker.read_text() == "current:trained.pkl"
def test_json_runner_pins_the_machines_proxy_before_the_project_env_loads(
tmp_path: Path,
):
"""The runner is a fresh interpreter that never imports cli.py. It reads the
machine's proxy and CA for AMP before `_run_json_crew` loads the project's
.env, so that file cannot choose where the saved login travels."""
from crewai_core import plus_api
current_pkg = tmp_path / "cli" / "crewai_cli"
current_pkg.mkdir(parents=True)
pinned = tmp_path / "pinned.json"
(current_pkg / "__init__.py").write_text("")
(current_pkg / "run_crew.py").write_text(
"import json\n"
"from pathlib import Path\n"
"from dotenv import load_dotenv\n"
"def _run_json_crew(trained_agents_file=None):\n"
" load_dotenv(Path.cwd() / '.env', override=True)\n"
" from crewai_core import plus_api\n"
f" Path({str(pinned)!r}).write_text(json.dumps(plus_api._MACHINE_TRANSPORT_ENV))\n"
)
(tmp_path / ".env").write_text(
"HTTPS_PROXY=http://project-proxy.test:8080\nSSL_CERT_FILE=/project/ca.pem\n"
)
env = {
key: value
for key, value in os.environ.items()
if not plus_api._sets_transport(key)
}
env["HTTPS_PROXY"] = "http://machine-proxy.test:3128"
env[run_crew_module._CREWAI_CLI_RUNNER_PACKAGE_DIR_ENV] = str(current_pkg)
subprocess.run(
[sys.executable, "-c", run_crew_module._JSON_CREW_RUNNER_CODE],
check=True,
env=env,
cwd=tmp_path,
)
assert json.loads(pinned.read_text()) == {
"HTTPS_PROXY": "http://machine-proxy.test:3128"
}
def test_json_runner_imports_with_older_project_env_crewai_core(tmp_path: Path):
old_parent = tmp_path / "old_env"
old_crewai_core = old_parent / "crewai_core"