From 3a6885d58c9d0066e39fac72d1be3a10eb3ef20c Mon Sep 17 00:00:00 2001 From: Rafid Saad Date: Sun, 13 Sep 2026 14:04:49 -0700 Subject: [PATCH] fix(cli): reject simultaneous workspace variables --- libs/cli/langgraph_cli/deploy.py | 19 ++++++---- .../tests/unit_tests/test_deploy_helpers.py | 36 +++++++++++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/libs/cli/langgraph_cli/deploy.py b/libs/cli/langgraph_cli/deploy.py index a4ecfd974..6a3418c76 100644 --- a/libs/cli/langgraph_cli/deploy.py +++ b/libs/cli/langgraph_cli/deploy.py @@ -1237,6 +1237,17 @@ def _create_host_backend_client( ) -> HostBackendClient: if env_vars is None: env_vars = _parse_env_from_config({}, pathlib.Path.cwd() / DEFAULT_CONFIG) + tenant_id = env_vars.get("LANGSMITH_TENANT_ID") or os.environ.get( + "LANGSMITH_TENANT_ID" + ) + workspace_id = env_vars.get("LANGSMITH_WORKSPACE_ID") or os.environ.get( + "LANGSMITH_WORKSPACE_ID" + ) + if tenant_id and workspace_id: + raise click.UsageError( + "LANGSMITH_TENANT_ID and LANGSMITH_WORKSPACE_ID cannot both be set. " + "Set only one." + ) resolved_api_key = api_key if not resolved_api_key: for key_name in _API_KEY_ENV_NAMES: @@ -1259,13 +1270,9 @@ def _create_host_backend_client( fg="yellow", ) resolved_api_key = click.prompt("Enter LangSmith API key", hide_input=True) - tenant_id = ( - env_vars.get("LANGSMITH_TENANT_ID") - or os.environ.get("LANGSMITH_TENANT_ID") - or env_vars.get("LANGSMITH_WORKSPACE_ID") - or os.environ.get("LANGSMITH_WORKSPACE_ID") + return HostBackendClient( + host_url, resolved_api_key, tenant_id=tenant_id or workspace_id ) - return HostBackendClient(host_url, resolved_api_key, tenant_id=tenant_id) def _call_host_backend_with_optional_tenant( diff --git a/libs/cli/tests/unit_tests/test_deploy_helpers.py b/libs/cli/tests/unit_tests/test_deploy_helpers.py index b3911d0bd..adfd4f003 100644 --- a/libs/cli/tests/unit_tests/test_deploy_helpers.py +++ b/libs/cli/tests/unit_tests/test_deploy_helpers.py @@ -542,20 +542,18 @@ class TestCreateHostBackendClientNoInput: @pytest.mark.parametrize("source", ["config", "shell"]) -@pytest.mark.parametrize("existing_tenant", [None, "existing-tenant"]) -def test_workspace_id_alias(monkeypatch, source, existing_tenant): +@pytest.mark.parametrize("name", ["LANGSMITH_TENANT_ID", "LANGSMITH_WORKSPACE_ID"]) +def test_workspace_id_alias(monkeypatch, source, name): monkeypatch.delenv("LANGSMITH_WORKSPACE_ID", raising=False) monkeypatch.delenv("LANGSMITH_TENANT_ID", raising=False) env_vars = {} if source == "config": - env_vars["LANGSMITH_WORKSPACE_ID"] = "workspace" + env_vars[name] = "workspace" else: - monkeypatch.setenv("LANGSMITH_WORKSPACE_ID", "workspace") - if existing_tenant: - monkeypatch.setenv("LANGSMITH_TENANT_ID", existing_tenant) + monkeypatch.setenv(name, "workspace") def handler(request): - assert request.headers["X-Tenant-ID"] == (existing_tenant or "workspace") + assert request.headers["X-Tenant-ID"] == "workspace" return httpx.Response(200, json={"resources": []}) monkeypatch.setattr( @@ -570,6 +568,30 @@ def test_workspace_id_alias(monkeypatch, source, existing_tenant): client._client.close() +@pytest.mark.parametrize("tenant_source", ["config", "shell"]) +@pytest.mark.parametrize("workspace_source", ["config", "shell"]) +@pytest.mark.parametrize("workspace_id", ["tenant-id", "workspace-id"]) +def test_rejects_both_workspace_names( + monkeypatch, tenant_source, workspace_source, workspace_id +): + env_vars = {} + for name, source, value in [ + ("LANGSMITH_TENANT_ID", tenant_source, "tenant-id"), + ("LANGSMITH_WORKSPACE_ID", workspace_source, workspace_id), + ]: + monkeypatch.delenv(name, raising=False) + if source == "config": + env_vars[name] = value + else: + monkeypatch.setenv(name, value) + + with pytest.raises( + click.UsageError, + match="LANGSMITH_TENANT_ID and LANGSMITH_WORKSPACE_ID cannot both be set", + ): + _create_host_backend_client("https://api.example.com", "test-key", env_vars) + + def test_workspace_id_is_not_uploaded_as_secret(): assert _secrets_from_env( {"LANGSMITH_WORKSPACE_ID": "workspace", "APP_SETTING": "value"}