diff --git a/backend/danswer/connectors/slack/connector.py b/backend/danswer/connectors/slack/connector.py index 859a9607e10..39576df69dd 100644 --- a/backend/danswer/connectors/slack/connector.py +++ b/backend/danswer/connectors/slack/connector.py @@ -232,6 +232,12 @@ def _default_msg_filter(message: MessageType) -> bool: return False +def _is_channel_id(value: str) -> bool: + """Slack channel IDs start with C (public) or G (private) followed by + alphanumeric characters, e.g. C04ABCDEF12.""" + return bool(re.fullmatch(r"[CG][A-Z0-9]{8,}", value)) + + def filter_channels( all_channels: list[dict[str, Any]], channels_to_connect: list[str] | None, @@ -241,6 +247,7 @@ def filter_channels( return all_channels if regex_enabled: + # regex only applies to channel names return [ channel for channel in all_channels @@ -250,19 +257,42 @@ def filter_channels( ) ] - # validate that all channels in `channels_to_connect` are valid - # fail loudly in the case of an invalid channel so that the user - # knows that one of the channels they've specified is typo'd or private + # separate channel IDs from channel names + channel_ids = {c for c in channels_to_connect if _is_channel_id(c)} + channel_names = {c for c in channels_to_connect if not _is_channel_id(c)} + all_channel_names = {channel["name"] for channel in all_channels} - for channel in channels_to_connect: - if channel not in all_channel_names: + all_channel_ids = {channel["id"] for channel in all_channels} + + # validate channel names — if a name is not found but valid channel IDs + # were also provided, warn instead of failing (the channel was likely + # renamed and the user also specified its stable ID) + for name in channel_names: + if name not in all_channel_names: + if channel_ids: + logger.warning( + f"Channel '{name}' not found in workspace. " + f"It may have been renamed. " + f"Consider using channel IDs instead for stability." + ) + else: + raise ValueError( + f"Channel '{name}' not found in workspace. " + f"Available channels: {all_channel_names}" + ) + + # validate that all channel IDs are valid + for cid in channel_ids: + if cid not in all_channel_ids: raise ValueError( - f"Channel '{channel}' not found in workspace. " - f"Available channels: {all_channel_names}" + f"Channel ID '{cid}' not found in workspace. " + f"Available channel IDs: {all_channel_ids}" ) return [ - channel for channel in all_channels if channel["name"] in channels_to_connect + channel + for channel in all_channels + if channel["name"] in channel_names or channel["id"] in channel_ids ] diff --git a/web/src/app/admin/connectors/slack/page.tsx b/web/src/app/admin/connectors/slack/page.tsx index 3b3cc678169..e8d6f82306a 100644 --- a/web/src/app/admin/connectors/slack/page.tsx +++ b/web/src/app/admin/connectors/slack/page.tsx @@ -228,8 +228,9 @@ const MainSection = () => { name: "channels", label: "Channels:", subtext: ` - Specify 0 or more channels to index. For example, specifying the channel - "support" will cause us to only index all content within the "#support" channel. + Specify 0 or more channels to index by name or channel ID. For example, + specifying "support" or "C04ABCDEF12" will index that channel. + Using channel IDs is recommended as they remain stable even if a channel is renamed. If no channels are specified, all channels in your workspace will be indexed.`, })(values)} { "Please enter the workspace to index" ), channels: Yup.array() - .of(Yup.string().required("Channel names must be strings")) + .of( + Yup.string().required("Channel names or IDs must be strings") + ) .required(), channel_regex_enabled: Yup.boolean().required(), })}