Skip to content

fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (#62219)#63951

Merged
BradGroux merged 2 commits intoopenclaw:mainfrom
sudie-codes:fix/msteams-dm-graph-media-62219-v2
Apr 10, 2026
Merged

fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (#62219)#63951
BradGroux merged 2 commits intoopenclaw:mainfrom
sudie-codes:fix/msteams-dm-graph-media-62219-v2

Conversation

@sudie-codes
Copy link
Copy Markdown
Contributor

Summary

Personal DM media fetches were failing because the plugin tried to use Graph /chats/{a:xxx}/messages/... which Graph rejects with "Invalid ThreadId" (only 19:xxx@thread.* / 19:xxx@unq.gbl.spaces IDs are accepted on /chats). File attachments (PDFs, documents) sent in Teams personal DMs were silently dropped with only a <media:document> placeholder reaching the agent.

Fix

  • Detect Bot Framework personal chat IDs (a: and 8:orgid: prefixes) via a new isBotFrameworkPersonalChatId helper.
  • Route media fetches for those IDs through the Bot Framework v3 attachments endpoint (GET {serviceUrl}/v3/attachments/{attachmentId} + .../views/{viewId}) using the bot's Bot Framework token scope.
  • Attachment IDs are extracted from the HTML attachments in the activity via the new extractMSTeamsHtmlAttachmentIds helper (un-sliced counterpart to the existing diagnostic summary).
  • When the conversation is a BF personal chat, skip the Graph fallback entirely so we do not waste a call that is guaranteed to fail with "Invalid ThreadId".
  • Keep the existing Graph routing intact for thread-format conversation IDs (19:xxx@thread.*) and group chats.
  • Wire activity.serviceUrl through resolveMSTeamsInboundMedia so the new code path has the Bot Framework service URL it needs.

Fixes #62219

Test plan

  • Unit test: a: and 8:orgid: IDs route to Bot Framework, Graph URLs never built
  • Unit test: 19: IDs continue to route to Graph (regression)
  • Unit test: missing serviceUrl leaves both paths inert (safe degradation)
  • Unit test: missing attachment IDs on BF DM logs a diagnostic
  • Unit test: bot-framework helper fetches attachment info then view and saves media
  • Unit test: bot-framework helper handles 401, missing views, oversized views, token failure
  • Full extensions/msteams suite passes (595 tests)
  • Manual: send a PDF in a personal DM with the bot

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: L labels Apr 9, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa85fc01cc

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +150 to +152
const baseUrl = `${normalizeServiceUrl(params.serviceUrl)}/v3/attachments/${encodeURIComponent(params.attachmentId)}`;
if (!isUrlAllowed(baseUrl, policy.allowHosts)) {
return undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce auth allowlist before sending Bot Framework token

downloadMSTeamsBotFrameworkAttachment only validates serviceUrl against policy.allowHosts, but both Bot Framework requests always include Authorization: Bearer .... That bypasses the repo’s existing authAllowHosts credential-bleed guard used in other attachment paths, so deployments that intentionally keep mediaAuthAllowHosts narrower than mediaAllowHosts can leak bot tokens to hosts that are download-allowed but not auth-allowed. Token-bearing BF requests should be gated by policy.authAllowHosts (or auth header stripped when outside that list).

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 9, 2026

Greptile Summary

This PR fixes personal DM media fetches in MS Teams by routing attachments with Bot Framework conversation IDs (a: and 8:orgid: prefixes) through the BF v3 attachments endpoint instead of Graph, which rejects those IDs as "Invalid ThreadId". The routing interacts correctly with the existing translateMSTeamsDmConversationIdForGraph call in message-handler.ts: when aadObjectId is available, a: IDs are translated to 19:…@unq.gbl.spaces and Graph is still used; the new BF path activates only when translation isn't possible or for 8:orgid: IDs, which is the intended design.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 observability suggestions that do not affect correctness.

The routing logic, SSRF guards, token handling, size limits, and Graph-skip semantics are all correct. Tests cover the key paths including regression for 19: IDs. The only open item is a missing debug log for the serviceUrl-absent edge case, which is a P2 improvement.

extensions/msteams/src/monitor-handler/inbound-media.ts — see P2 comment about silent skip when serviceUrl is absent for a BF DM

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/inbound-media.ts
Line: 68-94

Comment:
**Missing diagnostic log when `serviceUrl` is absent for a Bot Framework DM**

When `hasHtmlAttachment` is true and `isBotFrameworkPersonalChatId(conversationId)` is true but `serviceUrl` is falsy, neither the Bot Framework nor the Graph path executes — and no log is emitted. The Graph path is also skipped (because `!isBotFrameworkPersonalChatId(conversationId)` is false), so media is silently dropped with no diagnostic. Adding a debug log here makes the skip observable when `activity.serviceUrl` is unexpectedly absent.

```suggestion
    if (hasHtmlAttachment && isBotFrameworkPersonalChatId(conversationId)) {
      if (!serviceUrl) {
        log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
          conversationType,
          conversationId,
        });
      } else {
        const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
        if (attachmentIds.length === 0) {
          log.debug?.("bot framework attachment ids unavailable", {
            conversationType,
            conversationId,
          });
        } else {
          const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
            serviceUrl,
            attachmentIds,
            tokenProvider,
            maxBytes,
            allowHosts,
            authAllowHosts: params.authAllowHosts,
            preserveFilenames,
          });
          if (bfMedia.media.length > 0) {
            mediaList = bfMedia.media;
          } else {
            log.debug?.("bot framework attachments fetch empty", {
              conversationType,
              attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length,
            });
          }
        }
      }
    }
```

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

Reviews (1): Last reviewed commit: "fix(msteams): fetch DM media via Bot Fra..." | Re-trigger Greptile

Comment on lines +68 to +94
if (hasHtmlAttachment && serviceUrl && isBotFrameworkPersonalChatId(conversationId)) {
const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
if (attachmentIds.length === 0) {
log.debug?.("bot framework attachment ids unavailable", {
conversationType,
conversationId,
});
} else {
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
serviceUrl,
attachmentIds,
tokenProvider,
maxBytes,
allowHosts,
authAllowHosts: params.authAllowHosts,
preserveFilenames,
});
if (bfMedia.media.length > 0) {
mediaList = bfMedia.media;
} else {
log.debug?.("bot framework attachments fetch empty", {
conversationType,
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length,
});
}
}
}
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.

P2 Missing diagnostic log when serviceUrl is absent for a Bot Framework DM

When hasHtmlAttachment is true and isBotFrameworkPersonalChatId(conversationId) is true but serviceUrl is falsy, neither the Bot Framework nor the Graph path executes — and no log is emitted. The Graph path is also skipped (because !isBotFrameworkPersonalChatId(conversationId) is false), so media is silently dropped with no diagnostic. Adding a debug log here makes the skip observable when activity.serviceUrl is unexpectedly absent.

Suggested change
if (hasHtmlAttachment && serviceUrl && isBotFrameworkPersonalChatId(conversationId)) {
const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
if (attachmentIds.length === 0) {
log.debug?.("bot framework attachment ids unavailable", {
conversationType,
conversationId,
});
} else {
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
serviceUrl,
attachmentIds,
tokenProvider,
maxBytes,
allowHosts,
authAllowHosts: params.authAllowHosts,
preserveFilenames,
});
if (bfMedia.media.length > 0) {
mediaList = bfMedia.media;
} else {
log.debug?.("bot framework attachments fetch empty", {
conversationType,
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length,
});
}
}
}
if (hasHtmlAttachment && isBotFrameworkPersonalChatId(conversationId)) {
if (!serviceUrl) {
log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
conversationType,
conversationId,
});
} else {
const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
if (attachmentIds.length === 0) {
log.debug?.("bot framework attachment ids unavailable", {
conversationType,
conversationId,
});
} else {
const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
serviceUrl,
attachmentIds,
tokenProvider,
maxBytes,
allowHosts,
authAllowHosts: params.authAllowHosts,
preserveFilenames,
});
if (bfMedia.media.length > 0) {
mediaList = bfMedia.media;
} else {
log.debug?.("bot framework attachments fetch empty", {
conversationType,
attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length,
});
}
}
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/inbound-media.ts
Line: 68-94

Comment:
**Missing diagnostic log when `serviceUrl` is absent for a Bot Framework DM**

When `hasHtmlAttachment` is true and `isBotFrameworkPersonalChatId(conversationId)` is true but `serviceUrl` is falsy, neither the Bot Framework nor the Graph path executes — and no log is emitted. The Graph path is also skipped (because `!isBotFrameworkPersonalChatId(conversationId)` is false), so media is silently dropped with no diagnostic. Adding a debug log here makes the skip observable when `activity.serviceUrl` is unexpectedly absent.

```suggestion
    if (hasHtmlAttachment && isBotFrameworkPersonalChatId(conversationId)) {
      if (!serviceUrl) {
        log.debug?.("bot framework attachment skipped (missing serviceUrl)", {
          conversationType,
          conversationId,
        });
      } else {
        const attachmentIds = extractMSTeamsHtmlAttachmentIds(attachments);
        if (attachmentIds.length === 0) {
          log.debug?.("bot framework attachment ids unavailable", {
            conversationType,
            conversationId,
          });
        } else {
          const bfMedia = await downloadMSTeamsBotFrameworkAttachments({
            serviceUrl,
            attachmentIds,
            tokenProvider,
            maxBytes,
            allowHosts,
            authAllowHosts: params.authAllowHosts,
            preserveFilenames,
          });
          if (bfMedia.media.length > 0) {
            mediaList = bfMedia.media;
          } else {
            log.debug?.("bot framework attachments fetch empty", {
              conversationType,
              attachmentCount: bfMedia.attachmentCount ?? attachmentIds.length,
            });
          }
        }
      }
    }
```

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62f4a995b1

ℹ️ 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".

// or `8:orgid:...`) which Graph's `/chats/{id}` endpoint rejects with
// "Invalid ThreadId". Fetch media via the Bot Framework v3 attachments
// endpoint instead, which speaks the same identifier space.
if (hasHtmlAttachment && isBotFrameworkPersonalChatId(conversationId)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route BF attachment flow using the raw conversation ID

resolveMSTeamsInboundMedia now gates the Bot Framework branch on isBotFrameworkPersonalChatId(conversationId), but this function is called from message-handler with conversationId already normalized via translateMSTeamsDmConversationIdForGraph for a: DMs when aadObjectId and appId are present. In that path, the ID becomes 19:...@unq.gbl.spaces, this condition is false, and the new Bot Framework media fetch never runs; the code falls back to Graph only. That leaves the new DM fix inactive for translated a: conversations instead of consistently using the BF attachment endpoint.

Useful? React with 👍 / 👎.

@BradGroux BradGroux merged commit ab9be8d into openclaw:main Apr 10, 2026
41 of 42 checks passed
steipete pushed a commit that referenced this pull request Apr 10, 2026
…on IDs (#62219) (#63951)

* fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (#62219)

* fix(msteams): log skipped BF DM media fetches

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…on IDs (openclaw#62219) (openclaw#63951)

* fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (openclaw#62219)

* fix(msteams): log skipped BF DM media fetches

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…on IDs (openclaw#62219) (openclaw#63951)

* fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (openclaw#62219)

* fix(msteams): log skipped BF DM media fetches

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…on IDs (openclaw#62219) (openclaw#63951)

* fix(msteams): fetch DM media via Bot Framework path for a: conversation IDs (openclaw#62219)

* fix(msteams): log skipped BF DM media fetches

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
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: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MS Teams: Graph API media fetch fails in personal DMs — Bot Framework conversation ID not recognized as Graph chat ID

2 participants