fix(msteams): bound remote media saves with header and idle timeouts#109030
fix(msteams): bound remote media saves with header and idle timeouts#109030hugenshen wants to merge 1 commit into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 11:34 AM ET / 15:34 UTC. Summary PR surface: Source +19, Tests +28. Total +47 across 7 files. Reproducibility: yes. at source level and in the supplied real-entry-point proof: route a response that yields headers and one chunk, then stalls, through downloadAndStoreMSTeamsRemoteMedia with direct fetching enabled. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Keep one shared Teams media timeout policy, retain forwarding coverage for every response-save caller, and merge once the channel owner accepts the deliberate idle-timeout compatibility boundary on the exact reviewed head. Do we have a high-confidence way to reproduce the issue? Yes, at source level and in the supplied real-entry-point proof: route a response that yields headers and one chunk, then stalls, through downloadAndStoreMSTeamsRemoteMedia with direct fetching enabled. Is this the best way to solve the issue? Yes. Reusing the existing media-runtime timeout contract and centralizing the policy in the Teams attachment helper is narrower and less drift-prone than adding a separate downloader or configuration surface. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against faf3dbdda833. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +19, Tests +28. Total +47 across 7 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (7 earlier review cycles)
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
32c8858 to
700269d
Compare
|
@clawsweeper re-review Updated for CI cleanup + entry-point proof:
Full transcript is in the updated PR body Real behavior proof section. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review Updated after CI cleanup + entry-point proof:
Please re-check with the updated PR body transcript. |
What Problem This Solves
Fixes an issue where Microsoft Teams inbound remote media saves could hang indefinitely when an attachment response returned headers and then stalled mid-body.
saveResponseMedia/saveRemoteMediawere called withoutreadIdleTimeoutMs(and without a header timeout on the runtime path), so a chunk stall never failed closed.Why This Change Was Made
Pass the same media timeout policy already used by Mattermost/Zalo/Tlon:
responseHeaderTimeoutMs: 120_000andreadIdleTimeoutMs: 30_000through every production caller that hands a response tosaveResponseMedia/saveRemoteMedia, not just the direct-fetch path.MSTEAMS_MEDIA_READ_IDLE_TIMEOUT_MS/MSTEAMS_MEDIA_RESPONSE_HEADER_TIMEOUT_MSlive inshared.tsso the SharePoint direct path, Bot Framework attachment view save, and Graph hosted-content$valuesave all reference one constant instead of drifting.User Impact
Before: A stalled Teams attachment download body could hang inbound media persistence forever — on the direct SharePoint path, the Bot Framework personal-chat attachment path, or the Graph hosted-content path.
After: All three attachment body reads fail closed after 30s of idle (and header waits after 120s on the guarded path), so the inbound turn can surface a media failure instead of hanging. Slow-but-progressing transfers still complete (idle resets on each chunk).
Evidence
extensions/msteams/src/attachments/shared.ts,remote-media.ts,bot-framework.ts,graph.ts, plus*.test.tsregressions assertingreadIdleTimeoutMs: 30_000is forwardeddownloadAndStoreMSTeamsRemoteMedia→saveRemoteMediaDirect→saveResponseMedia({ readIdleTimeoutMs: 30_000 }), and the non-direct path →saveRemoteMedia({ responseHeaderTimeoutMs: 120_000, readIdleTimeoutMs: 30_000 })saveBotFrameworkAttachmentView→saveResponseMedia({ readIdleTimeoutMs: 30_000 })$value→saveResponseMedia({ readIdleTimeoutMs: 30_000 })scripts/proof-msteams-media-idle-timeout.mtsand local-only.pr-bodies/from the merge commit (oxlint/knip noise). Proof stays ephemeral under/tmp.upstream/mainand squashed to one commit (700269d7ffb).Real behavior proof
Behavior or issue addressed
Stalled Teams remote-media bodies fail closed on the channel's production 30s idle budget when driven through
downloadAndStoreMSTeamsRemoteMedia(not by callingsaveResponseMediaalone). Completing and slow-but-progressing transfers still save.Canonical reachability path
downloadAndStoreMSTeamsRemoteMedia({ useDirectFetch: true, fetchImpl })→saveRemoteMediaDirect→saveResponseMedia({ readIdleTimeoutMs: MSTEAMS_MEDIA_READ_IDLE_TIMEOUT_MS })Sibling Bot Framework / Graph callers share the same idle constant and are covered by focused vitest forwarding regressions.
Shared helper / provider constraint check
Uses existing
saveResponseMediaidle-read contract fromopenclaw/plugin-sdk/media-runtime. No new downloader or config key.Real environment tested
Live
node:httpserver on127.0.0.1(headers + one PNG chunk then stall; completing/small; slow/tricklewith per-chunk gaps under idle but total wall time above idle). Production Teams entrydownloadAndStoreMSTeamsRemoteMediawithuseDirectFetch: trueagainst those sockets. TempOPENCLAW_STATE_DIRmedia store. Node v22.23.1, head700269d7ffb137835775a13323c3031e188050d7.Exact steps or command run after this patch
# Ephemeral driver (not committed): node --import tsx /tmp/proof-msteams-media-idle-timeout.mtsEvidence after fix
Observed result after fix
Against a real stalled
image/pngsocket, the production Teams entrydownloadAndStoreMSTeamsRemoteMediafails closed in ~30095ms withMedia download stalled: no data received for 30000ms. UnboundedarrayBuffer()on the same socket is still hanging after 2000ms. A completing attachment still saves through the same entry. A trickle whose total time (48031ms) exceeds the 30000ms idle budget still saves, because idle resets on each chunk.What was not tested
Live stall against a real Microsoft Teams attachment host, Bot Framework connector, or Graph endpoint. Bot Framework / Graph entry points were not driven against the live socket in this proof (argument-forwarding regressions cover those call sites).
Fix classification
bugfix — timeout/hang
AI Assistance
AI-assisted. Author reviewed the Teams media save timeout wiring, CI cleanup (removed unused proof script), rebase onto latest main, and the entry-point real-socket proof output.