fix(msteams): fix inline pasted image downloads#50044
fix(msteams): fix inline pasted image downloads#50044jlian wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes inline pasted image downloads in MS Teams conversations by adding a Key changes:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/msteams/src/attachments/graph.ts
Line: 224-225
Comment:
**Response body not cancelled when content-length exceeds limit**
When `contentLength > params.maxBytes`, the branch is taken but the response body is never consumed or explicitly cancelled. In Node.js (undici-backed `fetch`), an unconsumed response body prevents the underlying connection from being returned to the pool. Under load — e.g., many messages containing oversized images — this can exhaust the HTTP connection pool.
`release()` unpins the dispatcher guard but does not drain or cancel the body stream.
The fix is to cancel the body before leaving the branch:
```suggestion
if (contentLength > params.maxBytes) {
// Skip oversized content without buffering; cancel the body to
// free the underlying connection back to the pool.
await valRes.body?.cancel();
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix(msteams): fix in..." |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff45fe5d56
ℹ️ 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".
ff45fe5 to
23f0831
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23f0831c2e
ℹ️ 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".
23f0831 to
8a1f348
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a1f348dd7
ℹ️ 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".
8a1f348 to
40a8ad1
Compare
|
All review comments addressed in the latest push. @greptileai @codex please re-review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40a8ad122a
ℹ️ 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".
40a8ad1 to
a56b641
Compare
|
All comments from round 2 addressed — P0 (wrong param names), P1s (release leak, duplicate media), P2 (test coverage). @greptileai @codex please re-review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a56b641114
ℹ️ 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".
a56b641 to
56f635c
Compare
|
Round 3 addressed: reverted broken dedup (P0), switched to streaming bounded read for maxBytes (P2), kept simple |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56f635ca8c
ℹ️ 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".
56f635c to
3ace8b0
Compare
|
Round 4: properly handles mixed file+inline messages by calling |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ace8b0463
ℹ️ 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".
3ace8b0 to
d6d1cb6
Compare
|
Round 5: fixed tokenProvider crash (P0 — was calling as function, now uses .getAccessToken()), fixed test to use contentBytes: null so $value path is actually exercised. @greptileai please re-review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6d1cb6d62
ℹ️ 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".
d6d1cb6 to
9a97d69
Compare
|
Round 6: wrapped mixed-message hostedContent path in try/catch (getAccessToken failure handling), added ssrfPolicy param. All 3 new P1s addressed. @greptileai please re-review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a97d69855
ℹ️ 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".
9a97d69 to
497da20
Compare
|
Round 7: gated Graph fallback on actual |
There was a problem hiding this comment.
💡 Codex Review
openclaw/extensions/msteams/src/attachments/graph.ts
Lines 444 to 445 in 497da20
Fresh evidence in this commit is that downloadGraphHostedContent() now fills hosted.media from $value even when contentBytes is null, but this return still concatenates hosted.media and attachmentMedia blindly. When the Graph /attachments payload also includes the message's text/html attachment, downloadMSTeamsAttachments() will re-download the same pasted image from its <img src>, so the new hosted-content recovery path can surface duplicate media entries for a single inline image.
ℹ️ 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".
497da20 to
b3be68a
Compare
|
Round 8: restored |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3be68a65b
ℹ️ 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".
| const hasHtmlAttachments = attachments.some((att) => | ||
| String(att.contentType ?? "").startsWith("text/html"), |
There was a problem hiding this comment.
Gate hostedContents fallback on actual inline-image evidence
In channel replies, extractMSTeamsConversationMessageId() keeps the thread-root ;messageid= suffix from conversation.id, and buildMSTeamsGraphMessageUrls() then probes both the reply URL and the root message URL. Because this new gate keys off any text/html attachment instead of actual inline-image markup, a reply that only contains rich-text HTML plus a normal file can now append the thread root's hosted image to the current message. Please gate this path on htmlSummary.imgTags (or equivalent inline-image evidence) rather than contentType === text/html alone.
Useful? React with 👍 / 👎.
| if (hosted.media.length > 0) { | ||
| mediaList = [...mediaList, ...hosted.media]; | ||
| break; |
There was a problem hiding this comment.
Deduplicate hostedContents against inline images already saved
If the HTML <img src> is already downloadable, downloadMSTeamsAttachments() has saved it earlier via extractInlineImageCandidates() in extensions/msteams/src/attachments/download.ts:208-227. This branch then appends hosted.media without any dedupe, so a pasted image that resolves both through the HTML URL and through Graph hostedContents will be surfaced twice. That gives downstream code duplicate media entries for a single image.
Useful? React with 👍 / 👎.
| const hosted = await downloadGraphHostedContent({ | ||
| messageUrl, | ||
| tokenProvider, | ||
| accessToken: token, | ||
| maxBytes, | ||
| allowHosts, | ||
| authAllowHosts: params.authAllowHosts, | ||
| preserveFilenames, |
There was a problem hiding this comment.
Reuse the configured fetch policy in the mixed hostedContents path
This mixed-message fast path calls downloadGraphHostedContent() directly, but unlike downloadMSTeamsGraphMedia() in extensions/msteams/src/attachments/graph.ts:403-410 it never threads through the ssrfPolicy derived from allowHosts. Deployments that intentionally narrow the Teams media allowlist will therefore still fetch messageUrl/hostedContents from Graph whenever an HTML attachment accompanies already-downloaded media, silently bypassing the configured host policy for that class of messages.
Useful? React with 👍 / 👎.
| if (contentLength > params.maxBytes) { | ||
| // Skip oversized content without buffering. |
There was a problem hiding this comment.
Response body not cancelled when content-length exceeds limit
When contentLength > params.maxBytes, the branch is taken but the response body is never consumed or explicitly cancelled. In Node.js (undici-backed fetch), an unconsumed response body prevents the underlying connection from being returned to the pool. Under load — e.g., many messages containing oversized images — this can exhaust the HTTP connection pool.
release() unpins the dispatcher guard but does not drain or cancel the body stream.
The fix is to cancel the body before leaving the branch:
| if (contentLength > params.maxBytes) { | |
| // Skip oversized content without buffering. | |
| if (contentLength > params.maxBytes) { | |
| // Skip oversized content without buffering; cancel the body to | |
| // free the underlying connection back to the pool. | |
| await valRes.body?.cancel(); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/attachments/graph.ts
Line: 224-225
Comment:
**Response body not cancelled when content-length exceeds limit**
When `contentLength > params.maxBytes`, the branch is taken but the response body is never consumed or explicitly cancelled. In Node.js (undici-backed `fetch`), an unconsumed response body prevents the underlying connection from being returned to the pool. Under load — e.g., many messages containing oversized images — this can exhaust the HTTP connection pool.
`release()` unpins the dispatcher guard but does not drain or cancel the body stream.
The fix is to cancel the body before leaving the branch:
```suggestion
if (contentLength > params.maxBytes) {
// Skip oversized content without buffering; cancel the body to
// free the underlying connection back to the pool.
await valRes.body?.cancel();
```
How can I resolve this? If you propose a fix, please make it concise.
Summary
Fixes inline pasted image downloads in MSTeams conversations. The Teams API returns
hostedContentsattachments withoutcontentBytes, so images are never saved.Changes
contentUrlusing the bot's bearer token whencontentBytesis missingTesting
Fixes #50043
Supersedes #10902 (closed by stale bot)
Related to #5448 (locked)