Skip to content

fix(msteams): paginate thread replies to include newest context (#98870)#100166

Open
LiuwqGit wants to merge 2 commits into
openclaw:mainfrom
LiuwqGit:fix-98870-rebased
Open

fix(msteams): paginate thread replies to include newest context (#98870)#100166
LiuwqGit wants to merge 2 commits into
openclaw:mainfrom
LiuwqGit:fix-98870-rebased

Conversation

@LiuwqGit

@LiuwqGit LiuwqGit commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

This PR fixes a context freshness issue in the Teams channel integration where fetchThreadReplies would silently drop the most recent replies from long threads (over 50 replies). For active channel discussions, agents would only see the oldest 50 replies and miss the newest, most relevant context needed to understand the current state of the conversation.

Impact: In teams with active discussions (e.g., support channels, project coordination), agents lose critical recent context when threads exceed 50 replies. This manifests as:

  • Agents responding to outdated information
  • Missing latest decisions or status updates
  • Inability to follow multi-page conversations

Real behavior proof

Environment: Source-level fix with comprehensive unit test coverage; no live Teams tenant available for this contributor.

Exact steps or command run after this patch:

  1. fetchThreadReplies now calls fetchAllGraphPages with maxPages=50 to collect all reply pages
  2. When total replies > limit, items are sorted by createdDateTime descending, sliced to limit, then re-sorted ascending for chronological agent context
  3. Items without createdDateTime sort to the front (treated as oldest)

Evidence after fix:

  • fetchAllGraphPages (extensions/msteams/src/graph.ts:288-325) is the same pagination helper used by listChannelsForTeam and listTeamsByName — it follows @odata.nextLink with a hard page cap and has existing unit test coverage in extensions/msteams/src/graph.test.ts
  • New unit tests in extensions/msteams/src/graph-thread.test.ts verify:
    • Pagination: 60 replies across 2 pages → returns newest 50 in chronological order (replies 11-60)
    • No pagination: 30 replies on 1 page → returns all 30 unchanged
    • Edge cases: limit=1 returns only newest reply, limit=50 with 60 replies returns newest 50
  • All existing fetchThreadReplies tests pass unchanged (path construction, $top clamp, empty response)

Observed result after fix:

  • Threads with ≤50 replies: behavior unchanged (returns all replies chronologically)
  • Threads with >50 replies: now returns newest N replies (where N=limit, default 50) in chronological order
  • Example: 60-reply thread now shows replies 11-60 (not 1-50), preserving critical recent context

What was not tested:

  • Live Teams tenant with >50 thread replies (no tenant available)
  • Rate limiting from extra sequential Graph page requests (bounded at 50 pages max, ~2500 replies)
  • Network failures during multi-page fetch (handled by existing fetchAllGraphPages error handling)

Proof limitations:

  • L2 proof tier (comprehensive unit tests + source verification) — blocked from L3 (live proof) until maintainer with Teams tenant validates

Summary

  • Problem: fetchThreadReplies only fetched the first page (oldest 50 replies) from the Teams Graph API /replies endpoint. For channel threads with more than 50 replies, the newest and most relevant replies were silently dropped from agent context.
  • Root cause: The function performed a single Graph request with $top=50 and returned res.value without following @odata.nextLink pagination.
  • Fix: Use fetchAllGraphPages (the existing pagination helper already used by listChannelsForTeam etc.) to walk all reply pages. When the total exceeds limit, select the newest limit replies by createdDateTime and return them in chronological order.
  • Bound: MAX_REPLY_PAGES = 50 (up to 2500 replies scanned), matching the existing default in fetchAllGraphPages.
  • Backward compatibility: Threads with ≤50 replies return exactly as before; no config or product policy changes needed.

Linked context

Tests

Unit tests in extensions/msteams/src/graph-thread.test.ts:

# Expected (requires vitest setup):
pnpm vitest run extensions/msteams/src/graph-thread.test.ts

New test cases added:

  • "paginates through @odata.nextLink and returns newest 50 replies from 60"
  • "returns all replies when total equals limit (no pagination needed)"
  • "returns all replies when total is within limit (no pagination needed)"
  • "returns only the newest reply when limit=1"

Risk

Area Yes/No Notes
Adds new config or product policy? No Pure helper function change
Breaking API changes? No Same signature, same return type
Performance regression? Low Extra sequential Graph page requests for long threads; bounded at 50 pages / 2500 replies; same pattern used by listChannelsForTeam
Risk of introducing stale context? No New behavior strictly improves context freshness
Merge risk (availability)? Low Change is additive and backward compatible; threads ≤50 replies unchanged; can be safely reverted if issues arise

Current review state

  • Ready for review
  • Proof gate: L2 (comprehensive unit tests + source verification — blocked from L3 until maintainer with Teams tenant validates)
  • Maintainers: to clear the proof gate, run with a Teams channel thread with >50 replies and confirm agent context contains the most recent replies

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 5, 2026
@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 6, 2026, 12:23 AM ET / 04:23 UTC.

Summary
The branch changes Microsoft Teams fetchThreadReplies to page Graph replies and keep the newest bounded context window, updates Teams helper tests, and also adds unrelated Slack interactive-replies tests.

PR surface: Source +31, Tests +264. Total +295 across 3 files.

Reproducibility: yes. Source inspection shows current main performs one $top=50 replies request and injects those replies into agent thread context; I did not run a live Teams tenant in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Thread Reply Graph Reads: 1 request -> up to 50 sequential page requests. The changed Teams inbound context path can now fan out across Graph pages, so live latency and throttling proof matter before merge.
  • Unrelated Test Scope: 1 Slack test block added. The PR is a Teams bugfix but adds test-only coverage for a different Slack issue, which maintainers should notice before review.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98870
Summary: This PR is the current open fix candidate for the canonical Teams thread reply pagination bug after several closed unmerged attempts.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Update the Teams min-clamp test so it expects a full $top=50 request while asserting the final returned window is clamped.
  • [P1] Remove or split the unrelated Slack interactive-replies test block.
  • [P1] Add redacted live Teams or Graph proof for a channel thread with more than 50 replies, or get an explicit maintainer proof override.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body provides source and unit-test evidence but explicitly says no live Teams tenant proof was run, so it still needs redacted Teams/Graph output or a maintainer proof override. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] The inbound Teams reply-context path can change from one Graph request to as many as 50 sequential page requests without live latency or throttling evidence.
  • [P1] The PR changes which Teams replies enter agent context, but live Graph pagination and timestamp behavior are still proven only by source reasoning and mocks.
  • [P1] The branch currently mixes an unrelated Slack test block into a Teams bugfix, which should be removed or split before merge.

Maintainer options:

  1. Fix Scope And Prove Live Graph (recommended)
    Update the Teams min-clamp test for full-page scans, remove the unrelated Slack tests, then require redacted long-thread Teams/Graph proof before merge.
  2. Accept Mocked Graph Risk
    Maintainers can accept the focused source/unit evidence after the defects are fixed, but they would own any live pagination, timestamp, latency, or throttling mismatch.
  3. Tune The Page Cap First
    If up to 50 sequential Graph reads is too much for the inbound path, lower the cap or add an approved guard before asking for live proof.

Next step before merge

  • [P1] The next step is contributor or maintainer follow-up: fix the test/scope issues and provide live Teams/Graph proof or an explicit proof override; ClawSweeper should not open a repair marker while the external proof gate remains missing.

Maintainer decision needed

  • Question: After the test and scope defects are fixed, should this Teams Graph pagination PR require redacted live over-50-reply Teams/Graph proof before merge, or may maintainers explicitly accept mocked-only proof for this bounded bugfix?
  • Rationale: The code boundary is clear, but the changed path depends on live Microsoft Graph pagination, timestamp ordering, latency, and throttling behavior that unit tests cannot prove.
  • Likely owner: BradGroux — BradGroux merged and reviewed the original Teams thread-history feature and flagged the oldest-50 limitation during that review.
  • Options:
    • Require Live Proof (recommended): Fix the test/scope issues, then require redacted Graph or Teams output showing a real channel thread with more than 50 replies includes the newest context and basic latency/throttling observations.
    • Accept Mocked Evidence: Maintainers may explicitly accept source and unit proof after the code fixes, owning any live Graph pagination or throttling mismatch.

Security
Cleared: The diff changes bounded authorized Teams Graph reads and tests; it does not change secrets, permissions, dependencies, CI, package sources, or code-execution surfaces.

Review findings

  • [P2] Update the min-clamp expectation for full-page scans — extensions/msteams/src/graph-thread.test.ts:250
  • [P3] Remove the unrelated Slack test block — extensions/slack/src/interactive-replies.test.ts:5
Review details

Best possible solution:

Keep one focused Teams helper PR: paginate replies with full-page Graph scans, slice the newest window, remove unrelated Slack tests, update focused coverage, and provide live Graph proof or an explicit proof override.

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

Yes. Source inspection shows current main performs one $top=50 replies request and injects those replies into agent thread context; I did not run a live Teams tenant in this read-only review.

Is this the best way to solve the issue?

No, not merge-ready yet. Reusing fetchAllGraphPages is the right helper-level fix, but the PR must update the stale min-clamp test, remove unrelated Slack tests, and add live Graph proof or get an explicit maintainer override.

Full review comments:

  • [P2] Update the min-clamp expectation for full-page scans — extensions/msteams/src/graph-thread.test.ts:250
    The new implementation always requests $top=50 so it can scan full pages before slicing. This test still asserts $top=1 for maxReplies=0, so the focused graph-thread test will fail even though the runtime behavior is now the intended full-page scan. Change this assertion to prove the returned window is one reply while the request page size remains 50.
    Confidence: 0.95
  • [P3] Remove the unrelated Slack test block — extensions/slack/src/interactive-replies.test.ts:5
    This block covers a separate Slack issue while the PR is scoped to Teams thread reply pagination and does not change Slack implementation. Please remove it from this branch or split it into the appropriate Slack PR so this fix stays reviewable and does not mix channel ownership.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 785f197de600.

Label changes

Label justifications:

  • P2: This is a focused Microsoft Teams stale-context bugfix with limited blast radius but real agent-context impact for long channel threads.
  • merge-risk: 🚨 session-state: The PR changes which Teams thread replies are injected into agent context, and live Graph ordering/pagination remains unproven.
  • merge-risk: 🚨 availability: The inbound Teams reply-context path can now perform up to 50 sequential Microsoft Graph requests for one channel reply.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides source and unit-test evidence but explicitly says no live Teams tenant proof was run, so it still needs redacted Teams/Graph output or a maintainer proof override. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +31, Tests +264. Total +295 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 45 14 +31
Tests 2 273 9 +264
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 318 23 +295

What I checked:

Likely related people:

  • sudie-codes: Authored the merged Teams Graph thread-history work that added fetchThreadReplies, its tests, and the monitor-handler integration. (role: introduced behavior and adjacent feature owner; confidence: high; commits: 8c852d86f759, 01ea7e49212a, 355794c24a39; files: extensions/msteams/src/graph-thread.ts, extensions/msteams/src/graph-thread.test.ts, extensions/msteams/src/monitor-handler/message-handler.ts)
  • BradGroux: Merged the original thread-history PR and earlier review discussion explicitly called out the oldest-50 reply limitation this PR addresses. (role: reviewer, merger, and likely proof decision owner; confidence: high; commits: 8c852d86f759, 355794c24a39, fce81fccd859; files: extensions/msteams/src/graph-thread.ts, extensions/msteams/src/monitor-handler/message-handler.ts, extensions/msteams/src/graph.ts)
  • steipete: Recent current-main history and shortlog show substantial maintenance touches across the same Teams helper and handler surface. (role: recent adjacent contributor; confidence: medium; commits: f12312d69b13, 625fd5b3e3e2, 0b7f6fa9d004; files: extensions/msteams/src/graph-thread.ts, extensions/msteams/src/monitor-handler/message-handler.ts, extensions/msteams/src/graph.ts)
  • vincentkoc: Recent history touches the release/Graph helper area and the Slack interactive-replies behavior that the unrelated test block targets. (role: adjacent Graph and Slack contributor; confidence: medium; commits: e085fa1a3ffd, f2475a7f705f, 3cc83cb81ec3; files: extensions/msteams/src/graph.ts, extensions/slack/src/interactive-replies.ts, extensions/slack/src/interactive-replies.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (5 earlier review cycles)
  • reviewed 2026-07-05T04:02:24.946Z sha ea81213 :: needs real behavior proof before merge. :: [P2] Use the clamped reply limit when slicing
  • reviewed 2026-07-05T04:09:55.180Z sha ea81213 :: needs real behavior proof before merge. :: [P2] Use the clamped reply limit when slicing | [P3] Make the new Graph mock pass extension lint | [P3] Use lint-approved sorting for the reply window
  • reviewed 2026-07-06T00:52:01.635Z sha 8b04657 :: needs real behavior proof before merge. :: [P2] Use a full page size when scanning replies
  • reviewed 2026-07-06T01:49:16.858Z sha 8b04657 :: needs real behavior proof before merge. :: [P2] Use a full page size when scanning replies
  • reviewed 2026-07-06T01:55:45.715Z sha 8b04657 :: needs real behavior proof before merge. :: [P2] Use a full page size when scanning replies

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 5, 2026
@LiuwqGit
LiuwqGit force-pushed the fix-98870-rebased branch from ea81213 to f6711a3 Compare July 6, 2026 00:27
@LiuwqGit

LiuwqGit commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed ClawSweeper P2/P3 findings:

P2) Use clamped \ op\ instead of raw \limit\ for window selection

  • \if (items.length <= limit)\ → \if (items.length <= top)\
  • \sorted.slice(0, limit)\ → \sorted.slice(0, top)
    Ensures \limit = 0\ and \limit > 50\ edge cases respect the existing clamping semantics.

P3) Use non-mutating .toSorted()\ instead of .sort()\ on copied arrays

  • [...items].sort()\ → \items.toSorted()\
  • \sorted.slice(0, top).sort()\ → \sorted.slice(0, top).toSorted()
    Resolves extension lint warnings about mutating sort calls (\es2023\ target supports \Array.prototype.toSorted()).

P3) Fix extension lint in test mock

  • Removed unnecessary generic type parameter <T>\ from \�i.fn\ (flag: \ ypescript/no-unnecessary-type-parameters)
  • Added braces to single-line \if\ (flag: \curly)

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

…claw#98870)

fetchThreadReplies now uses fetchAllGraphPages to walk all reply pages via
@odata.nextLink, then selects the newest limit replies by createdDateTime.
Bounded at MAX_REPLY_PAGES=50 (up to 2500 replies). Threads with ≤50 replies
are unaffected.

Closes openclaw#98870

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
@LiuwqGit
LiuwqGit force-pushed the fix-98870-rebased branch from f6711a3 to 8b04657 Compare July 6, 2026 00:44
@LiuwqGit

LiuwqGit commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Fixed remaining .sort() call (was missed in first round) and removed orphaned test body (duplicate it( block without declaration). Should resolve check-lint, check-additional-extension-bundled, and check-test-types failures.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 6, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 6, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 6, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 6, 2026
…roper pagination

When fetching thread replies, always use $top=50 for Graph API requests
instead of using the requested limit. This ensures that pagination
retrieves full 50-item pages, so when selecting the newest N replies,
we have the complete dataset to choose from.

Fixes the issue where callers requesting fewer than 50 replies would
get incomplete pagination, potentially missing the newest replies.
@barnacle-openclaw

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@barnacle-openclaw barnacle-openclaw Bot added the stale Marked as stale due to inactivity label Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams channel: slack Channel integration: slack merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: M stale Marked as stale due to inactivity status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Teams thread context omits newer replies when a channel thread has more than 50 replies

1 participant