Skip to content

fix(msteams): detect implicit mentions in thread replies via conversation.id#41108

Open
vongohren wants to merge 4 commits intoopenclaw:mainfrom
vongohren:fix/implicit-mention-thread-root-fallback
Open

fix(msteams): detect implicit mentions in thread replies via conversation.id#41108
vongohren wants to merge 4 commits intoopenclaw:mainfrom
vongohren:fix/implicit-mention-thread-root-fallback

Conversation

@vongohren
Copy link
Copy Markdown

Summary

Fixes thread reply implicit mention detection for MS Teams. When requireMention: true, replying to a bot message in a thread without @mention now correctly bypasses the mention gate.

Two changes:

  1. Fallback to conversation.id;messageid= for implicit mention — Teams doesn't reliably send replyToId, but it does encode the thread root message ID in conversation.id. The existing extractMSTeamsConversationMessageId() function already parses this; now it's wired into the implicit mention check.

  2. Cache thread root ID on send — The conversation.id;messageid= points to the thread root (the user's original message), not the bot's reply. So we also record the thread root ID in the sent-message cache when the bot replies, enabling the lookup to succeed.

Important: RSC required

This fix also requires ChannelMessage.Read.Group RSC permission in the Teams app manifest. Without RSC, Teams doesn't deliver thread replies to the bot webhook at all — the messages never reach OpenClaw. This is a Teams platform limitation documented in MicrosoftDocs/msteams-docs#4530.

Test plan

  • 8 new unit tests for implicit mention fallback logic (all pass)
  • 4 new unit tests for extractMSTeamsConversationMessageId (all pass)
  • Verified in production with RSC enabled:
    • @mention → responded ✅
    • Thread reply without @mention → responded (implicit mention) ✅
    • Multiple thread replies → all responded ✅
    • Top-level message without @mention → correctly ignored ✅

Closes #38629

🤖 Generated with Claude Code

vongohren and others added 2 commits March 7, 2026 07:10
Teams doesn't reliably send replyToId in thread replies. Fall back to
the thread root message ID extracted from conversation.id (the
;messageid=XXX suffix) when checking if the bot sent a message in that
thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…lies

The previous commit added conversation.id;messageid= as a fallback for
implicit mention detection, but the lookup still failed because the
sent-message cache only contained the bot's reply IDs, not the thread
root ID that Teams puts in conversation.id.

This commit also records the thread root message ID (conversationMessageId)
when the bot sends a reply, so subsequent thread replies match correctly.

Note: This fix requires ChannelMessage.Read.Group RSC permission in the
Teams app manifest for thread replies to reach the bot at all.

Tested and verified in production.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@openclaw-barnacle openclaw-barnacle bot added channel: msteams Channel integration: msteams size: S labels Mar 9, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR fixes implicit mention detection for MS Teams thread replies by adding a fallback that uses the thread-root message ID encoded in conversation.id;messageid= when replyToId is absent — a known Teams platform limitation. The bot also now caches the inbound thread-root ID at reply-send time so the lookup succeeds for subsequent thread replies.

Key points:

  • Logic is sound: The closure over conversationId / conversationMessageId in onSentMessageIds is correctly scoped to handleTeamsMessageNow, and the normalized ID used for recording matches the one used for lookup in handleTeamsMessage.
  • Test helper duplicates production logic: computeImplicitMention in message-handler.implicit-mention.test.ts is a copy of lines 685–693 of the production code rather than importing them. A regression in the actual production code block would not be caught by these tests.
  • Semantic mismatch in the cache: recordMSTeamsSentMessage is now used to store user message IDs (thread roots) alongside bot reply IDs. Since wasMSTeamsMessageSent is only used for the implicit-mention check this is functionally harmless, but the naming is misleading for future maintainers.
  • The existing extractMSTeamsConversationMessageId function and sent-message-cache TTL/size limits are adequate for this usage.

Confidence Score: 4/5

  • This PR is safe to merge — the logic is correct and well-contained, with no risk of false negatives or security issues.
  • The core logic change is small, consistent, and correctly scoped. Closure variables are captured from the right scope, the normalized conversation ID is used consistently for both recording and lookup, and the 24-hour TTL / 200-entry size cap in the sent-message cache keeps memory usage bounded. The main gap is that the new test file tests a hand-rolled copy of the production logic rather than the real code path, meaning a future regression in the actual implicitMention block would not be caught by these tests. No functional bugs were found.
  • Pay close attention to message-handler.implicit-mention.test.ts — the computeImplicitMention helper is a duplicate of production logic and should be replaced with a direct import or integration test to be meaningful.

Comments Outside Diff (1)

  1. extensions/msteams/src/monitor-handler/message-handler.ts, line 555-561 (link)

    Caching user message IDs in a "sent message" cache

    recordMSTeamsSentMessage / wasMSTeamsMessageSent are semantically "messages the bot sent." Here we're storing the inbound thread-root ID (i.e. the user's original message) in that same cache. This works because wasMSTeamsMessageSent is currently only used for the implicit-mention check, but the name implies it contains only bot-authored messages, which could confuse future maintainers or introduce subtle bugs if the cache is ever queried for a different purpose.

    Consider either renaming the cache (e.g. MSTeamsThreadCache / recordMSTeamsThreadAnchor) to reflect its broader role, or extracting the thread-root tracking into a separate, purpose-named cache so the semantics stay clear.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/msteams/src/monitor-handler/message-handler.ts
    Line: 555-561
    
    Comment:
    **Caching user message IDs in a "sent message" cache**
    
    `recordMSTeamsSentMessage` / `wasMSTeamsMessageSent` are semantically "messages the bot sent." Here we're storing the inbound thread-root ID (i.e. the user's original message) in that same cache. This works because `wasMSTeamsMessageSent` is currently only used for the implicit-mention check, but the name implies it contains only bot-authored messages, which could confuse future maintainers or introduce subtle bugs if the cache is ever queried for a different purpose.
    
    Consider either renaming the cache (e.g. `MSTeamsThreadCache` / `recordMSTeamsThreadAnchor`) to reflect its broader role, or extracting the thread-root tracking into a separate, purpose-named cache so the semantics stay clear.
    
    How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 826c5dd

Comment on lines +26 to +40
*/
function computeImplicitMention(activity: {
conversation?: { id?: string };
replyToId?: string;
}): boolean {
const rawConversationId = activity.conversation?.id ?? "";
const conversationId = normalizeMSTeamsConversationId(rawConversationId);
const replyToId = activity.replyToId ?? undefined;
const threadRootId = extractMSTeamsConversationMessageId(rawConversationId);
return Boolean(
conversationId &&
((replyToId && wasMSTeamsMessageSent(conversationId, replyToId)) ||
(threadRootId && wasMSTeamsMessageSent(conversationId, threadRootId))),
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test helper duplicates production logic instead of exercising it

The computeImplicitMention helper defined here is a re-implementation of the actual production code in message-handler.ts lines 685–693. Because the tests call this local function rather than the real code path, a regression introduced in the production implicitMention block would go completely undetected by these tests — they'd keep passing since the local re-implementation is unchanged.

Consider either:

  • Exporting the implicit mention computation as a small, pure utility in message-handler.ts (or a dedicated helper file) that both the production code and these tests can import directly, or
  • Testing through a lightweight version of the handleTeamsMessage entry point using dependency injection/mocks for the debouncer and dispatcher, so the real code is exercised.

As written, these tests are effectively testing the test helper itself rather than the production implementation.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/message-handler.implicit-mention.test.ts
Line: 26-40

Comment:
**Test helper duplicates production logic instead of exercising it**

The `computeImplicitMention` helper defined here is a re-implementation of the actual production code in `message-handler.ts` lines 685–693. Because the tests call this local function rather than the real code path, a regression introduced in the production `implicitMention` block would go completely undetected by these tests — they'd keep passing since the local re-implementation is unchanged.

Consider either:
- Exporting the implicit mention computation as a small, pure utility in `message-handler.ts` (or a dedicated helper file) that both the production code and these tests can import directly, or
- Testing through a lightweight version of the `handleTeamsMessage` entry point using dependency injection/mocks for the debouncer and dispatcher, so the real code is exercised.

As written, these tests are effectively testing the test helper itself rather than the production implementation.

How can I resolve this? If you propose a fix, please make it concise.

Extract the implicit mention computation from message-handler.ts into a
dedicated implicit-mention.ts module. Both the production code and the
tests now import the same function, so regressions in the logic will be
caught by the test suite.

Addresses review feedback about test helper duplicating production logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@BradGroux
Copy link
Copy Markdown
Contributor

Hi @vongohren — thanks for the submission. I’m the new Microsoft Teams maintainer for OpenClaw. Please give me a day or two to work through the open Teams backlog. Also, join the Twitter community for daily MS Teams feedback + updates: https://x.com/i/communities/2031170403607974228

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: msteams implicit mention fails — replyToId unreliable, conversation.id threadRootId not used as fallback

2 participants