From febd2af391c2ebc70724767d41565eb3805d1fab Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 03:41:57 -0700 Subject: [PATCH] fix(gateway): skip credential-less WhatsApp on secondary multiplex profiles _start_one_profile_adapters skipped only Platform.RELAY as shared process-level ingress. WhatsApp is the same shape: the bridge is one authenticated session tied to a single phone number, so a secondary profile has no credential of its own to bring; constructing an adapter for it only produced a connect/retry loop that stalled startup for every profile queued behind it. Treat WhatsApp like Relay -- the active profile owns the connection and route-stamped source.profile fans inbound turns out to secondary profiles. Salvage of #69042 (narrowed by its author to this one behavioral line); test re-expressed on the current secondary-startup fixtures. Co-authored-by: sshawn <28279366+lsshawn@users.noreply.github.com> --- gateway/run.py | 13 +++++-- .../test_multiplex_adapter_registry.py | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index 862fe4f5b7..b0b799f385 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -17146,12 +17146,17 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew platform.value, ) continue - # Relay is shared process-level ingress in multiplex mode. The - # active profile owns the one connection; connector-stamped - # source.profile routes inbound turns to secondary profiles. + # Relay and WhatsApp are shared process-level ingress in multiplex + # mode: one connection owned by the active profile, with + # route-stamped source.profile fanning inbound turns out to + # secondary profiles. The WhatsApp bridge is a single authenticated + # session tied to one phone number -- a secondary profile has no + # credential of its own to bring, so constructing an adapter for it + # only yields a connect/retry loop that stalls startup for every + # profile queued behind it. if ( getattr(self.config, "multiplex_profiles", False) - and platform is Platform.RELAY + and platform in (Platform.RELAY, Platform.WHATSAPP) ): continue try: diff --git a/tests/gateway/test_multiplex_adapter_registry.py b/tests/gateway/test_multiplex_adapter_registry.py index af2063d5e0..972701ddaf 100644 --- a/tests/gateway/test_multiplex_adapter_registry.py +++ b/tests/gateway/test_multiplex_adapter_registry.py @@ -1034,6 +1034,43 @@ class TestSecondaryProfileConfigHandling: assert "reviewer" in str(exc_info.value) assert "reviewer" not in runner._profile_adapters + @pytest.mark.asyncio + async def test_secondary_profile_adapter_start_skips_whatsapp(self, monkeypatch): + """WhatsApp is shared process-level ingress like Relay: the bridge is + one authenticated session tied to a single phone number, so a + credential-less secondary profile must be skipped (not stall startup + in a connect/retry loop) while its other platforms start normally.""" + runner = _secondary_recovery_runner() + direct = _SecondaryRecoveryAdapter() + _install_secondary_reconnect_context(monkeypatch, runner, direct) + monkeypatch.setattr( + "gateway.config.load_gateway_config", + lambda: GatewayConfig( + multiplex_profiles=True, + platforms={ + Platform.WHATSAPP: PlatformConfig(enabled=True), + Platform.DISCORD: PlatformConfig(enabled=True, token="profile-token"), + }, + ), + ) + factory_calls = [] + + def _create_adapter(platform, config): + factory_calls.append(platform) + return direct + + async def _connect(adapter, platform): + return True + + monkeypatch.setattr(runner, "_create_adapter", _create_adapter) + monkeypatch.setattr(runner, "_connect_initial_adapter_with_timeout", _connect) + + connected = await runner._start_one_profile_adapters("clientbot", "/tmp/x", {}) + + assert connected == 1 + assert factory_calls == [Platform.DISCORD] + assert runner._profile_adapters["clientbot"] == {Platform.DISCORD: direct} + class TestSecondaryProfileHookRegistration: """A secondary profile's own `hooks:` block must register on ITS