fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility#41838
Conversation
Greptile SummaryThis PR fixes a key mismatch in the MS Teams allowlist resolution: at startup, the Graph API GUID was stored as the team key, but at runtime Bot Framework sends the General channel's conversation ID as Strengths:
Concern:
Confidence Score: 3/5
Last reviewed commit: 781f45b |
There was a problem hiding this comment.
Pull request overview
Fixes MS Teams allowlist resolution to use the same team identifier that Bot Framework sends at runtime (channelData.team.id), preventing inbound channel messages from being dropped due to a key mismatch.
Changes:
- Resolve a team’s config key to the General channel conversation ID (instead of the Graph group GUID).
- Fetch team channels once during resolution and reuse them for channel matching.
- Extend/adjust unit tests to cover team-only resolution and the “General missing” fallback.
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 resolution to prefer the General channel conversation ID and reuses a single channel list lookup. |
| extensions/msteams/src/resolve-allowlist.test.ts | Updates expectations and adds test cases for team-only entries and the fallback path. |
… 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
89ef482 to
8a77dc5
Compare
|
Landed via temp rebase onto main. Thanks @BradGroux! |
上游更新摘要(abb8f6310 → bda63c3,164 commits): ### 新功能 - ACP: 新增 resumeSessionId 支持 ACP session 恢复(openclaw#41847) - CLI: 新增 openclaw backup create/verify 本地状态归档命令(openclaw#40163) - Talk: 新增 talk.silenceTimeoutMs 配置项,可自定义静默超时(openclaw#39607) - ACP Provenance: 新增 ACP 入站溯源元数据和回执注入(openclaw#40473) - Brave 搜索: 新增 llm-context 模式,返回 AI 精炼摘要(openclaw#33383) - browser.relayBindHost: Chrome relay 可绑定非 loopback 地址(WSL2 支持)(openclaw#39364) - node-pending-work: 新增 node.pending.pull/ack RPC 接口 - Telegram: 新增 exec-approvals 处理器,支持 Telegram 内命令执行审批 - Mattermost: 新增 target-resolution,修复 markdown 保留和 DM media 上传 - MS Teams: 修复 Bot Framework General channel 对话 ID 兼容性(openclaw#41838) - secrets/runtime-web-tools: 全新 web runtime secrets 工具模块 - cron: 新增 store-migration,isolated-agent 直送核心通道,delivery failure notify - TUI: 自动检测浅色终端主题(COLORFGBG),支持 OPENCLAW_THEME 覆盖(openclaw#38636) ### 修复 - macOS: launchd 重启前重启已禁用服务,修复 openclaw update 卡死问题 - Telegram DM: 按 agent 去重入站 DM,防止同一条消息触发重复回复(openclaw#40519) - Matrix DM: 修复 m.direct homeserver 检测,保留房间绑定优先级(openclaw#19736) - 飞书: 清理插件发现缓存,修复 onboarding 安装后重复弹窗(openclaw#39642) - config/runtime snapshots: 修复 config 写入后 secret 快照丢失问题(openclaw#37313) - browser/CDP: 修复 ws:// CDP URL 反向代理和 wildcard 地址重写 - agents/failover: 识别 Bedrock tokens per day 限额为 rate limit ### 版本 - ACPX 0.1.16 - iOS/macOS 版本号更新 - Android: 精简后台权限 构建验证:待执行
* main: (43 commits) docs: add openclaw#42173 to CHANGELOG — strip leaked model control tokens (openclaw#42216) Agents: align onPayload callback and OAuth imports docs: add Tengji (George) Zhang to maintainer table (openclaw#42190) fix: strip leaked model control tokens from user-facing text (openclaw#42173) Changelog: add unreleased March 9 entries chore: add .dev-state to .gitignore (openclaw#41848) fix(agents): avoid duplicate same-provider cooldown probes in fallback runs (openclaw#41711) fix(mattermost): preserve markdown formatting and native tables (openclaw#18655) feat(acp): add resumeSessionId to sessions_spawn for ACP session resume (openclaw#41847) ACPX: bump bundled acpx to 0.1.16 (openclaw#41975) mattermost: fix DM media upload for unprefixed user IDs (openclaw#29925) fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility (openclaw#41838) fix(mattermost): read replyTo param in plugin handleAction send (openclaw#41176) fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro (openclaw#40757) fix(ui): replace Manual RPC text input with sorted method dropdown (openclaw#14967) CI: select Swift 6.2 toolchain for CodeQL (openclaw#41787) fix(agents): forward memory flush write path (openclaw#41761) fix(telegram): move network fallback to resolver-scoped dispatchers (openclaw#40740) fix(security): harden replaceMarkers() to catch space/underscore boundary marker variants (openclaw#35983) fix(web-search): recover OpenRouter Perplexity citations from message annotations (openclaw#40881) ...
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
… Framework compatibility (openclaw#41838) * fix(msteams): use General channel conversation ID as team key for Bot 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 * fix(msteams): handle listChannelsForTeam failure gracefully * fix(msteams): trim General channel ID and guard against empty string * fix: document MS Teams allowlist team-key fix (openclaw#41838) (thanks @BradGroux) --------- Co-authored-by: bradgroux <[email protected]> Co-authored-by: Onur <[email protected]>
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.listChannelsForTeamthrows (rate limit, network error), falls back to the Graph GUID rather than failing the entire resolution. This preserves the pre-fix behavior where team-only entries never called the channels API.Testing
Unit tests (
extensions/msteams/src/resolve-allowlist.test.ts):teamIdis now the General channel's conversation ID, not the Graph GUID.All 6 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
Supersedes #41453 (closed due to branch deletion during conflict resolution)