fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility#41453
fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility#41453BradGroux wants to merge 1 commit intoopenclaw:mainfrom
Conversation
… Framework compatibility Bot Framework sends `activity.channelData.team.id` as the General channel's conversation ID (e.g. `19:[email protected]`), not the Graph API group GUID (e.g. `fa101332-cf00-431b-b0ea-f701a85fde81`). The startup resolver was storing the Graph GUID as the team config key, so runtime matching always failed and every channel message was silently dropped. Fix: always call `listChannelsForTeam` during resolution to find the General channel, then use its conversation ID as the stored `teamId`. When a specific channel is also configured, reuse the same channel list rather than issuing a second API call. Falls back to the Graph GUID if the General channel cannot be found (renamed/deleted edge case). Fixes openclaw#41390
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d618c71809
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/compat"; | ||
| import { searchGraphUsers } from "./graph-users.js"; | ||
| import { |
There was a problem hiding this comment.
Replace unresolved imports at top of resolver module
The new imports in resolve-allowlist.ts are not resolvable in this tree: openclaw/plugin-sdk/compat is not exported by package.json (only ./plugin-sdk is), and there are no local extensions/msteams/src/graph*.ts modules backing ./graph.js or ./graph-users.js. This causes module-resolution failures when the MS Teams resolver is loaded (and during type-check/build), so the extension cannot run outside the mocked unit test context.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Fixes MS Teams allowlist resolution so the stored team key matches what Bot Framework sends at runtime (channelData.team.id) by deriving the key from the General channel conversation ID.
Changes:
- Update Teams allowlist resolution to use the General channel’s conversation ID as
teamId(fallback to Graph GUID if unavailable). - Refactor allowlist resolution to use a shared mapping helper (
mapAllowlistResolutionInputs) and a shared Graph user search helper. - Add unit tests covering the new resolution behavior and fallback path.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| extensions/msteams/src/resolve-allowlist.ts | Changes team key derivation to use General channel conversation ID; refactors resolution flow to a helper-based mapping approach. |
| extensions/msteams/src/resolve-allowlist.test.ts | Adds unit tests validating the new team key behavior, including fallback when General isn’t found. |
| vi.mock("./graph.js", () => ({ | ||
| listTeamsByName, | ||
| listChannelsForTeam, | ||
| normalizeQuery, | ||
| resolveGraphToken, | ||
| })); | ||
|
|
||
| vi.mock("./graph-users.js", () => ({ | ||
| searchGraphUsers, | ||
| })); |
There was a problem hiding this comment.
The test mocks ./graph.js and ./graph-users.js, but those modules are not present under extensions/msteams/src in this PR. Vitest will fail to resolve these imports even if the functions are mocked. Add the missing modules (or adjust the test to mock the actual existing module(s) used by resolve-allowlist.ts).
| import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk/compat"; | ||
| import { searchGraphUsers } from "./graph-users.js"; | ||
| import { | ||
| listChannelsForTeam, | ||
| listTeamsByName, | ||
| normalizeQuery, | ||
| resolveGraphToken, | ||
| } from "./graph.js"; |
There was a problem hiding this comment.
The new imports point to modules that don't exist in this repo (openclaw/plugin-sdk/compat, ./graph.js, ./graph-users.js). As-is, this file will fail to compile/run. Either add the missing modules (and ensure they are exported via package exports), or revert to the previous in-file Graph helpers / import from an existing local module (e.g. factor the shared Graph helpers into a new extensions/msteams/src/graph.ts and update imports accordingly).
Greptile SummaryThis PR fixes a critical silent message-drop bug in the MS Teams extension where inbound channel messages were never processed. The root cause was a key format mismatch: during startup, Key changes:
All tests pass, and the fix is fully backward-compatible with existing configurations. Confidence Score: 4/5
Last reviewed commit: d618c71 |
Summary
When OpenClaw starts up and resolves the MS Teams channel allowlist, it queries the Microsoft Graph API for team info and stores the Graph API group GUID (e.g.
fa101332-cf00-431b-b0ea-f701a85fde81) as the team key in its allowlist config. At runtime, the Bot Framework sendsactivity.channelData.team.idas the General channel's conversation ID (e.g.19:[email protected]) — a completely different format. The two keys never match, so every inbound channel message is silently dropped regardless of config. This PR fixes the mismatch by resolving the General channel's conversation ID during startup and storing that as the team key instead.Problem
Observed behavior
After configuring a Teams channel in OpenClaw, channel messages are never processed. No errors appear — messages just vanish. The configured team matches by display name at startup, the
resolved: trueresponse looks correct, but at runtime nothing gets through.Diagnostic evidence
Patching the message handler to log
activity.channelData:{ "team": { "id": "19:[email protected]", "name": "Digital Meld" }, "channel": { "id": "19:[email protected]" } }The startup resolver stored
fa101332-cf00-431b-b0ea-f701a85fde81(Graph GUID). The runtime key is19:[email protected](General channel conversation ID). These never match.A workaround confirmed the root cause: manually replacing the Graph GUID in config with the General channel's conversation ID caused messages to flow correctly.
Root Cause
The Microsoft Graph API
/teamsendpoint returns groups by their AAD group GUID (e.g.fa101332-cf00-431b-b0ea-f701a85fde81). This is whatlistTeamsByNamereturns asteam.id.The Bot Framework, however, identifies the team context of a channel message using the General channel's conversation ID — the
19:…@thread.tacv2format used for all Teams conversations. These are two different identifiers for the same team, and there is no documented 1:1 mapping without calling the channels API.resolveMSTeamsChannelAllowlistwas using the Graph GUID directly asteamIdin the resolved config entry. At runtime, the allowlist check compareschannelData.team.id(conversation ID format) against the storedteamId(GUID format) — a comparison that can never succeed.Fix
In
resolveMSTeamsChannelAllowlist'smapInputcallback, after obtaininggraphTeamId(the Graph GUID), the fix:listChannelsForTeam(token, graphTeamId)— necessary to find the General channel regardless of whether a specific channel was requested.displayName === "general"(case-insensitive).teamId— this matches what Bot Framework sends aschannelData.team.idat runtime.Testing
Unit tests (
extensions/msteams/src/resolve-allowlist.test.ts):teamIdis now the General channel's conversation ID, not the Graph GUID.All 5 tests pass:
Manual validation: Confirmed the workaround (manually setting the General channel conversation ID as config key) makes messages flow correctly. This fix automates that resolution at startup.
Breaking Changes
None. This is fully backward compatible:
/^[0-9a-fA-F-]{16,}$/), it goes throughlistChannelsForTeamagainst that ID, which will still resolve correctly.Secondary Issue (noted, not fixed here)
The compiled logger wrapper in the MS Teams extension swallows structured data —
debug()andinfo()only pass the first argument to the underlying logger, silently dropping context objects. This made diagnosing the root cause significantly harder. Tracked separately.Related
Fixes #41390