Skip to content

fix(msteams): fix image attachment download for channel and DM messages#40463

Closed
hddevteam wants to merge 5 commits intoopenclaw:mainfrom
hddevteam:fix/msteams-hostedcontent-value
Closed

fix(msteams): fix image attachment download for channel and DM messages#40463
hddevteam wants to merge 5 commits intoopenclaw:mainfrom
hddevteam:fix/msteams-hostedcontent-value

Conversation

@hddevteam
Copy link
Copy Markdown

@hddevteam hddevteam commented Mar 9, 2026

Problem

When Teams users send images or file attachments, the bot silently drops them. This PR fixes multiple root causes discovered in a cross-tenant deployment (Bot App registered in Azure subscription tenant A, Teams conversations in M365 org tenant B). All fixes have been validated in production.


Fixes

1. Fetch hostedContents via /$value when contentBytes not inlined

The Graph API /hostedContents collection endpoint never returns contentBytes inline in the list response. The original code treated an empty contentBytes as "no content" and continued, so every attachment was skipped unconditionally.

Fix: when contentBytes is absent but the item has an id, fall back to fetching the binary via /{id}/$value, which reliably returns the file content. The Content-Type header from that response is also captured for accurate MIME detection.

2. some() instead of every() for HTML attachment detection (inbound-media.ts)

When a channel message contains a mix of image and text/html attachments, every() required ALL attachments to be html before attempting the Graph path. This silently skipped image attachments in mixed messages.

Fix: changed to some() so the Graph path is attempted whenever at least one attachment has a text/html content type.

3. Add trafficmanager.net to DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST (shared.ts)

DM image attachments use direct download URLs on smba.trafficmanager.net (SMBA = Service Messaging Bot Attachments, served via Azure Traffic Manager). This domain was missing from the auth token allowlist, so requests were sent without a Bearer token, resulting in 401 and silent download failures.

Fix: added "trafficmanager.net" to DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST.

4. Prefer channelData.chatId for DM/group chats; add /chats/ fallback for channel messages (graph.ts)

Two related fixes in buildMSTeamsGraphMessageUrls:

  • DM/group: conversationId may be a Bot Framework conversation ID (a:xxx) not accepted by Graph. Prefer channelData.chatId (the proper Graph chat GUID) when available.
  • Channel: channelData.team.id is sometimes a Teams thread ID (19:xxx@thread.tacv2) instead of a GUID. The /teams/{guid}/channels/ endpoint rejects this with 400. /chats/{threadId}/messages/ accepts thread-format IDs, so /chats/ URLs are now appended as fallback candidates after the /teams/ URLs.

Testing

  • All 72 msteams unit tests pass (15 test files fail due to a pre-existing missing @mariozechner/pi-ai dependency unrelated to this PR).
  • Validated end-to-end in production: channel images ✅, DM images ✅.

Copilot AI review requested due to automatic review settings March 9, 2026 02:19
@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: XS labels Mar 9, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 9, 2026

Greptile Summary

This PR fixes a real bug where Teams image/file attachments were silently dropped because the Microsoft Graph API never returns contentBytes inline in the /hostedContents collection response. The fix adds a fallback to fetch individual content via the /{id}/$value endpoint when contentBytes is absent, correctly capturing the MIME type from the response content-type header.

Key changes in extensions/msteams/src/attachments/graph.ts:

  • buffer is now Buffer | undefined with a final !buffer guard before use — safe and correct.
  • The /$value fetch is wrapped in a try...finally ensuring valRelease() is always called, including when continue is hit on a non-ok response.
  • encodeURIComponent(item.id) properly sanitises the hosted-content ID in the constructed URL.
  • The SSRF policy and access token are forwarded consistently with the rest of the file.
  • mime ?? itemContentType ?? undefined on the saveMediaBuffer call has a redundant trailing ?? undefined (since itemContentType is already string | undefined), but this is harmless.

Confidence Score: 4/5

  • This PR is safe to merge — it correctly implements the Microsoft Graph /$value fallback with proper resource release, SSRF protection, and error handling.
  • The logic is straightforward and well-contained to a single function. valRelease() is always called via finally, encodeURIComponent handles special characters in item IDs, and the SSRF policy is consistently applied. No critical bugs were found. Score is 4 rather than 5 only because the change adds sequential per-item HTTP requests (one per attachment) with no streaming size guard, which could be slow or memory-intensive for messages with many large attachments — but this is a performance consideration rather than a correctness issue.
  • No files require special attention.

Last reviewed commit: 830077f

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: 830077f60f

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

Comment thread extensions/msteams/src/attachments/graph.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes MSTeams Graph hosted content downloads by handling the Graph API behavior where contentBytes is not inlined in /hostedContents list responses, ensuring Teams images/files aren’t silently dropped.

Changes:

  • Decode hosted content from contentBytes when present; otherwise fetch bytes via .../hostedContents/{id}/$value.
  • Capture content-type from the $value response and feed it into MIME detection / storage.

Comment thread extensions/msteams/src/attachments/graph.ts Outdated
Comment thread extensions/msteams/src/attachments/graph.ts Outdated
Comment thread extensions/msteams/src/attachments/graph.ts Outdated
Comment thread extensions/msteams/src/attachments/graph.ts Outdated
@hddevteam
Copy link
Copy Markdown
Author

The two failing CI checks are pre-existing issues unrelated to this PR:

  1. check (format:check)src/cli/daemon-cli/lifecycle.test.ts has a formatting issue. This file is not touched by this PR.
  2. checks-windows (6/6)src/secrets/runtime.test.ts fails with ACL verification unavailable on Windows — a Windows-specific secrets path permission issue, also unrelated to this change.

Both failures reproduce on upstream/main and were present before this branch was created.

@BradGroux
Copy link
Copy Markdown
Member

Hi @hddevteam — 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

@hddevteam hddevteam force-pushed the fix/msteams-hostedcontent-value branch from 2090dd9 to eec664e Compare March 11, 2026 01:01
@hddevteam hddevteam changed the title fix(msteams): fetch hostedContents via /$value when contentBytes not inlined fix(msteams): fix image attachment download for channel and DM messages Mar 11, 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: eec664e0cb

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

Comment thread extensions/msteams/src/attachments/shared.ts Outdated
@hddevteam
Copy link
Copy Markdown
Author

Totally understand, @BradGroux — no rush at all! Thanks for taking on the Teams maintainer role and for letting us know. Looking forward to your review whenever you get a chance. 🙏

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: 4039a52195

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

Comment thread extensions/msteams/src/attachments/graph.ts Outdated
@hddevteam hddevteam force-pushed the fix/msteams-hostedcontent-value branch from 4039a52 to f43d155 Compare March 17, 2026 06:43
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: 442621bb7e

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

Comment thread extensions/msteams/src/attachments/graph.ts Outdated
@vincentkoc
Copy link
Copy Markdown
Member

This assigned pull request has been marked as stale after being open for 27 days.
Please add updates or it will be closed.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 26, 2026

Codex review: needs maintainer review before merge.

What this changes:

The PR updates Microsoft Teams Graph attachment handling to add channel /chats fallback URLs, prefer channelData.chatId for chat media URLs, read hosted-content $value responses with the shared bounded helper, normalize response MIME hints, and add oversized hosted-content tests.

Maintainer follow-up before merge:

This is an active, focused contributor PR already assigned to the Microsoft Teams maintainer; the remaining action is domain validation and normal PR review rather than a separate ClawSweeper repair branch.

Security review:

Security review cleared: The latest diff is limited to Teams attachment code and tests, adds no dependencies or CI/release changes, uses bounded response reading, and no longer broadens bearer-token forwarding to all Traffic Manager subdomains.

Review details

Best possible solution:

Keep the fix inside the Microsoft Teams plugin, land the focused Graph/media hardening after maintainer validation of the /chats fallback, and keep broader personal-DM attachment coverage tracked in #65329 unless this branch is explicitly expanded and tested for that case.

Do we have a high-confidence way to reproduce the issue?

Yes, for code-level reproduction: current main visibly returns only /teams channel URL candidates, prefers conversationId before channelData.chatId, and buffers hosted $value responses with arrayBuffer(). Live cross-tenant Teams reproduction was not attempted under the read-only constraint, but the PR body reports production validation.

Is this the best way to solve the issue?

Yes, with a domain-validation caveat: the latest branch keeps the behavior in the owning Teams plugin, reuses the existing bounded response helper, and backs out the broad Traffic Manager auth allowlist change. The /chats channel fallback should still be reviewed by the Teams maintainer before merge.

Acceptance criteria:

  • pnpm test extensions/msteams/src/attachments/graph.test.ts extensions/msteams/src/attachments.helpers.test.ts extensions/msteams/src/monitor-handler/inbound-media.test.ts extensions/msteams/src/attachments.test.ts
  • pnpm exec oxfmt --check --threads=1 extensions/msteams/src/attachments/graph.ts extensions/msteams/src/attachments.test.ts

What I checked:

  • Current main lacks channel /chats fallback: On current main, the channel branch of buildMSTeamsGraphMessageUrls only constructs /teams/{teamId}/channels/{channelId}/messages... URLs and returns without appending /chats/{conversationId}/messages... candidates. (extensions/msteams/src/attachments/graph.ts:75, 9061d1e4c3ee)
  • Current main still prefers conversationId over channelData.chatId: For non-channel Graph media URLs, current main computes chatId from params.conversationId before falling back to channelData.chatId, which is one of the orderings the PR changes. (extensions/msteams/src/attachments/graph.ts:108, 9061d1e4c3ee)
  • Current main still buffers hosted $value responses: The hosted-content $value path checks Content-Length, then calls arrayBuffer() before the final byte-length guard, so lengthless or misreported responses are buffered before enforcement. (extensions/msteams/src/attachments/graph.ts:228, 9061d1e4c3ee)
  • Bounded helper exists and is already used by Teams media code: readResponseWithLimit enforces the byte cap while reading the response stream; the Teams remote-media path already imports and uses it for direct media fetches. (src/media/read-response-with-limit.ts:123, 9061d1e4c3ee)
  • Latest PR diff keeps auth allowlist narrow: The final patch removes the earlier broad trafficmanager.net auth allowlist addition; current main already allows authenticated retries for smba.trafficmanager.net specifically. (extensions/msteams/src/attachments/shared.ts:70, e0d695aef9dc)
  • PR discussion assigns domain review: The PR is open, mergeable, assigned to BradGroux, and the discussion records BradGroux stating he is the Microsoft Teams maintainer; the refreshed Microsoft tracker also lists this PR as open and assigned. (e0d695aef9dc)

Likely related people:

  • BradGroux: He assigned himself in the PR timeline, stated he is the Microsoft Teams maintainer, is listed as assignee in the refreshed Microsoft tracker, and appears as co-author on recent merged Teams DM media work. (role: Microsoft Teams maintainer / reviewer candidate; confidence: high; commits: ab9be8dba547; files: extensions/msteams/src/monitor-handler/inbound-media.ts, extensions/msteams/src/attachments/graph.ts)
  • sudie-codes: Recent merged commits by this author changed the same Teams attachment/media paths for Bot Framework DM downloads, SharePoint/OneDrive media, Node 24 media fetches, and channel HTML fallback behavior. (role: recent Teams media fix author; confidence: high; commits: ab9be8dba547, 4fc5016f8fd2, 2084441b51aa; files: extensions/msteams/src/attachments/graph.ts, extensions/msteams/src/attachments/shared.ts, extensions/msteams/src/monitor-handler/inbound-media.ts)
  • steipete: History for the central files includes the move of Microsoft Teams into the plugin, security-sensitive Teams hardening, and recent import/performance work around Teams attachment modules. (role: repository maintainer / recent adjacent owner; confidence: medium; commits: d9f9e93deeac, c56b56e514f8, 55389f4be293; files: extensions/msteams/src/attachments/graph.ts, extensions/msteams/src/attachments/shared.ts, extensions/msteams/src/monitor-handler/inbound-media.ts)
  • vincentkoc: Recent history shows a merged Teams graph media diagnostics fix on the same inbound-media/Graph path, and the PR timeline includes stale-label maintenance by this handle. (role: recent adjacent maintainer; confidence: medium; commits: b62251817e57; files: extensions/msteams/src/attachments/graph.ts, extensions/msteams/src/monitor-handler/inbound-media.ts)

Remaining risk / open question:

  • Live Microsoft Graph validation for the proposed channel /chats/{threadId} fallback was not performed in this read-only review; the Teams maintainer should confirm that endpoint behavior for the reported tenant shapes.
  • The broader personal-DM attachment gap in bug(msteams): DM inline images and file attachments silently dropped #65329 remains open, so merging this PR should not be treated as closing every DM inline-image/file case.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 9061d1e4c3ee.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 27, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 27, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 28, 2026
@hddevteam hddevteam force-pushed the fix/msteams-hostedcontent-value branch from 16fbd98 to 7239407 Compare April 28, 2026 17:27
Ming added 5 commits April 29, 2026 07:29
…inlined

Graph API's /hostedContents collection endpoint does not return contentBytes
inline in the response — the field is always null/absent. The previous code
would skip every item with an empty contentBytes, causing all image/file
attachments sent via Teams to be silently dropped.

Fix: when contentBytes is absent but the item has an id, fall back to fetching
the actual bytes via the individual /{id}/$value endpoint, which always returns
the binary content. The content-type header from that response is also used to
set itemContentType for accurate MIME detection downstream.

Verified working against Microsoft Teams (channel messages) with both image
and file attachments.
…LIST

DM image attachments in Microsoft Teams use a direct download URL on
smba.trafficmanager.net (the Bot Framework attachment service, SMBA =
Service Messaging Bot Attachments). This domain was absent from the auth
token allowlist, so download requests were sent without a Bearer token,
resulting in 401 responses and silent download failures.

Add "trafficmanager.net" so that Bot Framework tokens are automatically
attached to requests targeting this domain.
…lback for channels

Two related fixes for buildMSTeamsGraphMessageUrls:

1. DM/group chat: prefer channelData.chatId (the proper Graph chat GUID)
   over conversationId, which may be a Bot Framework conversation ID in
   the form "a:xxx" that Graph does not recognise.

2. Channel messages: Teams channel IDs surfaced in channelData.team.id
   are sometimes in thread format ("19:xxx@thread.tacv2") rather than a
   GUID. The /teams/{guid}/channels/ Graph endpoint rejects these with
   400. /chats/{threadId}/messages/ accepts thread-format IDs, so append
   /chats/ candidate URLs as a fallback after the /teams/ URLs.
@hddevteam hddevteam force-pushed the fix/msteams-hostedcontent-value branch from 7239407 to e0d695a Compare April 28, 2026 23:30
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 29, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 29, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 30, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 30, 2026
@barnacle-openclaw
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

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 stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants