From c7fd3eb37703370e304ecb898a07a30969658f51 Mon Sep 17 00:00:00 2001 From: davidgut1982 Date: Tue, 23 Jun 2026 13:21:11 +0000 Subject: [PATCH] fix(gateway): honor explicit api_server enabled:false under env key _apply_env_overrides() force-set ``api_server.enabled = True`` whenever API_SERVER_KEY (or API_SERVER_ENABLED) was present in the environment. In multiplex mode, a secondary profile pins ``platforms.api_server.enabled: false`` in its config.yaml so that it shares the default profile's API-server listener instead of binding its own port. That profile still inherits the process-level env, including API_SERVER_KEY, so the unconditional re-enable flipped api_server back on and tripped the MultiplexConfigError check. Honor an explicit disable, flagged by ``_enabled_explicit`` in the platform's extra. Use ``extra.pop("_enabled_explicit", False)``: the api_server branch is terminal (unlike the migrated plugin platforms, no later registry pass re-enables api_server), so popping consumes the flag in a single read and avoids the double-read hazard, while the final per-platform cleanup remains a no-op. Adds a regression test asserting that with API_SERVER_KEY set, a config with api_server explicitly enabled:false + _enabled_explicit:true survives _apply_env_overrides() as enabled=False (fails without the fix), while the key is still wired through for the shared listener. Co-Authored-By: Claude Opus 4.8 --- gateway/config.py | 14 +++++++++++++- tests/gateway/test_config.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/gateway/config.py b/gateway/config.py index cc80cda40155..b43485fefeb9 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -2011,7 +2011,19 @@ def _apply_env_overrides(config: GatewayConfig) -> None: if api_server_enabled or api_server_key: if Platform.API_SERVER not in config.platforms: config.platforms[Platform.API_SERVER] = PlatformConfig() - config.platforms[Platform.API_SERVER].enabled = True + # Respect an explicit ``enabled: false`` in config.yaml (flagged by + # ``_enabled_explicit``). In multiplex mode a secondary profile's + # config.yaml pins ``platforms.api_server.enabled: false`` so it shares + # the default profile's listener instead of binding its own port. That + # profile still inherits the process-level env (including + # ``API_SERVER_KEY``); without this guard the env-var presence would + # force-enable the listener and trip the MultiplexConfigError check. + # Pop (don't read) the marker — the api_server branch is terminal (no + # later registry pass re-enables it), so this both consumes the flag and + # avoids reading it twice, matching the pop convention used elsewhere. + api_server_explicit = config.platforms[Platform.API_SERVER].extra.pop("_enabled_explicit", False) + if not api_server_explicit or config.platforms[Platform.API_SERVER].enabled: + config.platforms[Platform.API_SERVER].enabled = True if api_server_key: config.platforms[Platform.API_SERVER].extra["key"] = api_server_key if api_server_cors_origins: diff --git a/tests/gateway/test_config.py b/tests/gateway/test_config.py index 910961dd7bf1..0b272ab74eb8 100644 --- a/tests/gateway/test_config.py +++ b/tests/gateway/test_config.py @@ -2117,3 +2117,36 @@ class TestMultiplexProfilesConfig: "Explicit top-level false was overridden by nested true — " "loader must respect top-level precedence when key is present" ) + + +class TestApiServerEnvOverride: + def test_env_key_does_not_reenable_explicitly_disabled_api_server(self): + """An explicit ``platforms.api_server.enabled: false`` must survive + _apply_env_overrides() even when API_SERVER_KEY is present in the env. + + Regression: _apply_env_overrides() force-set api_server.enabled = True + whenever API_SERVER_KEY (or API_SERVER_ENABLED) was set. In multiplex + mode a secondary profile pins ``api_server.enabled: false`` so it shares + the default profile's listener instead of binding its own port, but it + still inherits the process-level API_SERVER_KEY. The unconditional + re-enable flipped it back on and tripped the MultiplexConfigError check. + + The fix honors the explicit disable, flagged by ``_enabled_explicit`` in + the platform's extra (set when the config.yaml pins enabled). + """ + config = GatewayConfig( + platforms={ + Platform.API_SERVER: PlatformConfig( + enabled=False, + extra={"_enabled_explicit": True}, + ), + }, + ) + + with patch.dict(os.environ, {"API_SERVER_KEY": "secret-key"}, clear=True): + _apply_env_overrides(config) + + # Explicit disable wins over the env-var presence. + assert config.platforms[Platform.API_SERVER].enabled is False + # The key is still wired through for the shared listener. + assert config.platforms[Platform.API_SERVER].extra.get("key") == "secret-key"