diff --git a/backend/app/gateway/routers/channel_connections.py b/backend/app/gateway/routers/channel_connections.py index 0733ff2890..ef25aa4545 100644 --- a/backend/app/gateway/routers/channel_connections.py +++ b/backend/app/gateway/routers/channel_connections.py @@ -747,8 +747,9 @@ async def _require_wechat_qr_login(request: Request) -> ChannelConnectionsConfig # QR sessions and mutation locks live in one process. Reject every QR route # before session access when requests could land on different workers. # WEB_CONCURRENCY is Uvicorn's fallback when no worker count is supplied. + # A blank value means "unset", as the compose command's ${GATEWAY_WORKERS:-1} treats it. try: - workers = int(os.environ.get("GATEWAY_WORKERS", os.environ.get("WEB_CONCURRENCY", "1"))) + workers = int(os.environ.get("GATEWAY_WORKERS") or os.environ.get("WEB_CONCURRENCY") or "1") except ValueError: workers = 0 if workers != 1: diff --git a/backend/packages/harness/deerflow/integrations/lark_broker.py b/backend/packages/harness/deerflow/integrations/lark_broker.py index 63c2d5a187..35dfa17c76 100644 --- a/backend/packages/harness/deerflow/integrations/lark_broker.py +++ b/backend/packages/harness/deerflow/integrations/lark_broker.py @@ -430,8 +430,9 @@ def _config_from_env() -> BrokerConfig: config_dir=os.environ.get("LARKSUITE_CLI_CONFIG_DIR", "/var/lark/config"), data_dir=os.environ.get("LARKSUITE_CLI_DATA_DIR", "/var/lark/data"), host=os.environ.get("DEERFLOW_LARK_BROKER_HOST", LARK_BROKER_DEFAULT_HOST), - port=int(os.environ.get("DEERFLOW_LARK_BROKER_PORT", str(LARK_BROKER_DEFAULT_PORT))), - timeout_seconds=int(os.environ.get("DEERFLOW_LARK_BROKER_TIMEOUT", str(LARK_BROKER_DEFAULT_TIMEOUT_SECONDS))), + # A blank value means "unset" (`-e DEERFLOW_LARK_BROKER_PORT=`); int("") would abort startup. + port=int(os.environ.get("DEERFLOW_LARK_BROKER_PORT") or LARK_BROKER_DEFAULT_PORT), + timeout_seconds=int(os.environ.get("DEERFLOW_LARK_BROKER_TIMEOUT") or LARK_BROKER_DEFAULT_TIMEOUT_SECONDS), deny_subcommands=parse_deny_subcommands(os.environ.get(LARK_BROKER_DENY_SUBCOMMANDS_ENV)), ) diff --git a/backend/tests/test_channel_connections_router.py b/backend/tests/test_channel_connections_router.py index 304eab7289..e48f008083 100644 --- a/backend/tests/test_channel_connections_router.py +++ b/backend/tests/test_channel_connections_router.py @@ -1226,7 +1226,7 @@ def test_disconnect_connection_is_current_user_scoped(tmp_path): anyio.run(repo.close) -@pytest.mark.parametrize("worker_env", [{}, {"GATEWAY_WORKERS": "1"}, {"WEB_CONCURRENCY": "1"}]) +@pytest.mark.parametrize("worker_env", [{}, {"GATEWAY_WORKERS": "1"}, {"WEB_CONCURRENCY": "1"}, {"GATEWAY_WORKERS": ""}]) def test_wechat_qr_login_saves_credentials_without_exposing_token(monkeypatch, worker_env): from app.channels.wechat_qr_login import WechatQRLogin @@ -1366,7 +1366,17 @@ def test_wechat_qr_confirmation_preserves_bot_id_unless_replaced(monkeypatch, re assert saved == expected -@pytest.mark.parametrize("worker_env", [{"GATEWAY_WORKERS": "2"}, {"GATEWAY_WORKERS": "4"}, {"WEB_CONCURRENCY": "2"}, {"GATEWAY_WORKERS": "invalid"}]) +@pytest.mark.parametrize( + "worker_env", + [ + {"GATEWAY_WORKERS": "2"}, + {"GATEWAY_WORKERS": "4"}, + {"WEB_CONCURRENCY": "2"}, + {"GATEWAY_WORKERS": "invalid"}, + # A blank GATEWAY_WORKERS is unset, so WEB_CONCURRENCY still decides. + {"GATEWAY_WORKERS": "", "WEB_CONCURRENCY": "2"}, + ], +) @pytest.mark.parametrize("method,suffix", [("POST", ""), ("POST", "/session/poll"), ("DELETE", "/session")]) def test_wechat_qr_routes_reject_unsupported_workers_before_session_access(monkeypatch, worker_env, method, suffix): from app.channels.wechat_qr_login import WechatQRLogin diff --git a/backend/tests/test_lark_broker.py b/backend/tests/test_lark_broker.py index 83e4c54820..77f557b380 100644 --- a/backend/tests/test_lark_broker.py +++ b/backend/tests/test_lark_broker.py @@ -355,3 +355,15 @@ def test_allowed_subcommand_still_runs_with_denylist(tmp_path: Path) -> None: assert result.exit_code == 0 payload = json.loads(result.stdout.decode()) assert payload["argv"] == ["auth", "status"] + + +@pytest.mark.parametrize("name", ["DEERFLOW_LARK_BROKER_PORT", "DEERFLOW_LARK_BROKER_TIMEOUT"]) +def test_config_from_env_treats_blank_numbers_as_unset(monkeypatch, name) -> None: + monkeypatch.delenv("DEERFLOW_LARK_BROKER_PORT", raising=False) + monkeypatch.delenv("DEERFLOW_LARK_BROKER_TIMEOUT", raising=False) + monkeypatch.setenv(name, "") + + config = lark_broker._config_from_env() + + assert config.port == lark_broker.LARK_BROKER_DEFAULT_PORT + assert config.timeout_seconds == lark_broker.LARK_BROKER_DEFAULT_TIMEOUT_SECONDS