Skip to content

Commit b2284f6

Browse files
feat: logic for ensuring bots dont reply ot threads where they were not mentioned
1 parent 29fd307 commit b2284f6

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

packages/slackBotFunction/app/slack/slack_handlers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def setup_handlers(app: App) -> None:
5050

5151
# ack function for events where we respond with eyes
5252
def respond_to_events(event: Dict[str, Any], ack: Ack, client: WebClient):
53-
if should_reply_to_message(event):
53+
if should_reply_to_message(event, client):
5454
respond_with_eyes(event=event, client=client)
5555
logger.debug("Sending ack response")
5656
ack()
@@ -103,9 +103,9 @@ def unified_message_handler(client: WebClient, event: Dict[str, Any], body: Dict
103103
# and its a message
104104
# and its not in a thread
105105
# then ignore it as it will be handled as an app_mention event
106-
if not should_reply_to_message(event):
107-
logger.debug("Ignoring message in group chat not in a thread", extra={"event": event})
108-
# ignore messages in group chats
106+
if not should_reply_to_message(event, client):
107+
logger.debug("Ignoring message in group chat not in a thread or bot not in thread", extra={"event": event})
108+
# ignore messages in group chats or threads where bot wasn't mentioned
109109
return
110110
user_id = event.get("user", "unknown")
111111
conversation_key, _ = conversation_key_and_root(event=event)

packages/slackBotFunction/app/utils/handler_utils.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,107 @@ def extract_session_pull_request_id(conversation_key: str) -> str | None:
211211
return None
212212

213213

214-
def should_reply_to_message(event: Dict[str, Any]) -> bool:
214+
def get_bot_user_id(client: WebClient) -> str | None:
215+
"""
216+
Get the bot's user ID using auth.test API.
217+
This is cached to avoid repeated API calls.
218+
"""
219+
if not hasattr(get_bot_user_id, "_cache"):
220+
get_bot_user_id._cache = {}
221+
222+
cache_key = id(client)
223+
224+
if cache_key in get_bot_user_id._cache:
225+
return get_bot_user_id._cache[cache_key]
226+
227+
try:
228+
auth_response = client.auth_test()
229+
if auth_response.get("ok"):
230+
bot_user_id = auth_response.get("user_id")
231+
get_bot_user_id._cache[cache_key] = bot_user_id
232+
logger.info("Cached bot user ID", extra={"bot_user_id": bot_user_id})
233+
return bot_user_id
234+
except Exception:
235+
logger.error("Error fetching bot user ID", extra={"error": traceback.format_exc()})
236+
237+
return None
238+
239+
240+
def was_bot_mentioned_in_thread_root(channel: str, thread_ts: str, client: WebClient) -> bool:
241+
"""
242+
Check if THIS specific bot was mentioned anywhere in the thread history.
243+
This handles cases where:
244+
- Multiple bots are in the same channel (checks for this bot's specific user ID)
245+
- Bot is mentioned later in a thread (not just the root message)
246+
"""
247+
try:
248+
# get this bot's user ID
249+
bot_user_id = get_bot_user_id(client)
250+
if not bot_user_id:
251+
logger.warning("Could not determine bot user ID, failing open")
252+
return True
253+
254+
response = client.conversations_replies(channel=channel, ts=thread_ts, inclusive=True)
255+
256+
if not response.get("ok") or not response.get("messages"):
257+
logger.warning("Failed to fetch thread messages", extra={"channel": channel, "thread_ts": thread_ts})
258+
return True
259+
260+
# check if THIS bot is mentioned in any message in the thread
261+
bot_mention_pattern = rf"<@{re.escape(bot_user_id)}(?:\|[^>]+)?>"
262+
263+
for message in response["messages"]:
264+
message_text = message.get("text", "")
265+
if re.search(bot_mention_pattern, message_text):
266+
logger.debug(
267+
"Found bot mention in thread",
268+
extra={
269+
"channel": channel,
270+
"thread_ts": thread_ts,
271+
"bot_user_id": bot_user_id,
272+
"message_ts": message.get("ts"),
273+
},
274+
)
275+
return True
276+
277+
logger.debug(
278+
"Bot not mentioned in thread",
279+
extra={"channel": channel, "thread_ts": thread_ts, "bot_user_id": bot_user_id},
280+
)
281+
return False
282+
283+
except Exception:
284+
logger.error("Error checking bot mention in thread", extra={"error": traceback.format_exc()})
285+
return True
286+
287+
288+
def should_reply_to_message(event: Dict[str, Any], client: WebClient = None) -> bool:
215289
"""
216290
Determine if the bot should reply to the message.
217291
218292
Conditions:
219293
should not reply if:
220294
- Message is in a group chat (channel_type == 'group') but not in a thread
295+
- Message is in a channel thread where the bot was not initially mentioned
221296
"""
222297

298+
# we don't reply to non-threaded messages in group chats
223299
if event.get("channel_type") == "group" and event.get("type") == "message" and event.get("thread_ts") is None:
224300
return False
301+
302+
# for channel threads, check if bot was mentioned anywhere in the thread history
303+
if event.get("channel_type") == "channel" and event.get("thread_ts"):
304+
if not client:
305+
logger.warning("No Slack client provided to check thread participation")
306+
return True
307+
308+
channel = event.get("channel")
309+
thread_ts = event.get("thread_ts")
310+
311+
if not was_bot_mentioned_in_thread_root(channel, thread_ts, client):
312+
logger.debug(
313+
"Bot not mentioned in thread, ignoring message", extra={"channel": channel, "thread_ts": thread_ts}
314+
)
315+
return False
316+
225317
return True

0 commit comments

Comments
 (0)