diff --git a/backend/app/channels/service.py b/backend/app/channels/service.py index 8d17f7481..8042733c2 100644 --- a/backend/app/channels/service.py +++ b/backend/app/channels/service.py @@ -23,6 +23,16 @@ _CHANNEL_REGISTRY: dict[str, str] = { "wecom": "app.channels.wecom:WeComChannel", } +# Keys that indicate a user has configured credentials for a channel. +_CHANNEL_CREDENTIAL_KEYS: dict[str, list[str]] = { + "discord": ["bot_token"], + "feishu": ["app_id", "app_secret"], + "slack": ["bot_token", "app_token"], + "telegram": ["bot_token"], + "wecom": ["bot_id", "bot_secret"], + "wechat": ["bot_token"], +} + _CHANNELS_LANGGRAPH_URL_ENV = "DEER_FLOW_CHANNELS_LANGGRAPH_URL" _CHANNELS_GATEWAY_URL_ENV = "DEER_FLOW_CHANNELS_GATEWAY_URL" @@ -88,7 +98,16 @@ class ChannelService: if not isinstance(channel_config, dict): continue if not channel_config.get("enabled", False): - logger.info("Channel %s is disabled, skipping", name) + cred_keys = _CHANNEL_CREDENTIAL_KEYS.get(name, []) + has_creds = any(not isinstance(channel_config.get(k), bool) and channel_config.get(k) is not None and str(channel_config[k]).strip() for k in cred_keys) + if has_creds: + logger.warning( + "Channel '%s' has credentials configured but is disabled. Set enabled: true under channels.%s in config.yaml to activate it.", + name, + name, + ) + else: + logger.info("Channel %s is disabled, skipping", name) continue await self._start_channel(name, channel_config) diff --git a/backend/tests/test_channels.py b/backend/tests/test_channels.py index e6fb0213f..bdb4584e5 100644 --- a/backend/tests/test_channels.py +++ b/backend/tests/test_channels.py @@ -2011,6 +2011,65 @@ class TestChannelService: assert service.manager._langgraph_url == "http://custom-langgraph:2024" assert service.manager._gateway_url == "http://custom-gateway:8001" + def test_disabled_channel_with_string_creds_emits_warning(self, caplog): + """Warning is emitted when a channel has string credentials but enabled=false.""" + import logging + + from app.channels.service import ChannelService + + async def go(): + service = ChannelService( + channels_config={ + "wecom": {"enabled": False, "bot_id": "corp123", "bot_secret": "secret"}, + } + ) + with caplog.at_level(logging.WARNING, logger="app.channels.service"): + await service.start() + await service.stop() + + _run(go()) + assert any("wecom" in r.message and r.levelno == logging.WARNING for r in caplog.records) + + def test_disabled_channel_with_int_creds_emits_warning(self, caplog): + """Warning is emitted even when YAML-parsed integer credentials are present.""" + import logging + + from app.channels.service import ChannelService + + async def go(): + # Simulate YAML parsing a numeric token/ID as an int + service = ChannelService( + channels_config={ + "telegram": {"enabled": False, "bot_token": 123456789}, + } + ) + with caplog.at_level(logging.WARNING, logger="app.channels.service"): + await service.start() + await service.stop() + + _run(go()) + assert any("telegram" in r.message and r.levelno == logging.WARNING for r in caplog.records) + + def test_disabled_channel_without_creds_emits_info(self, caplog): + """Only an info log (no warning) is emitted when a channel is disabled with no credentials.""" + import logging + + from app.channels.service import ChannelService + + async def go(): + service = ChannelService( + channels_config={ + "telegram": {"enabled": False}, + } + ) + with caplog.at_level(logging.DEBUG, logger="app.channels.service"): + await service.start() + await service.stop() + + _run(go()) + warning_records = [r for r in caplog.records if "telegram" in r.message and r.levelno == logging.WARNING] + assert not warning_records + # --------------------------------------------------------------------------- # Slack send retry tests