Skip to content

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

Closed
LiuwqGit wants to merge 1 commit into
openclaw:mainfrom
LiuwqGit:fix/issue-98870-teams-thread-pagination
Closed

fix(msteams): paginate thread replies to include newest context (#98870)#100155
LiuwqGit wants to merge 1 commit into
openclaw:mainfrom
LiuwqGit:fix/issue-98870-teams-thread-pagination

Conversation

@LiuwqGit

@LiuwqGit LiuwqGit commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

Real behavior proof

Environment: Source-level fix; no live Teams tenant available for this contributor.

Steps:

  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:

  • fetchAllGraphPages (graph.ts:288) is the same helper used by listChannelsForTeam and listTeamsByName — it follows @odata.nextLink with a hard page cap and has existing unit test coverage in graph.test.ts
  • New unit tests in graph-thread.test.ts:
    • 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
  • All existing fetchThreadReplies tests pass unchanged (path, $top clamp, empty)

Untested:

  • Live Teams tenant with >50 thread replies (no tenant available)
  • Rate limiting from extra sequential Graph page requests (bounded at 50 pages max)

Proof limitations:

  • L1 (unit tests only) — blocked until live proof from a maintainer with a Teams tenant
  • The createdDateTime sort is best-effort: items without timestamps sort to the front, so they are dropped first when over limit

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 is within limit (no pagination needed)"

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
Risk of introducing stale context? No New behavior strictly improves context freshness

Current review state

  • Ready for review
  • Proof gate: L1 (unit tests only — blocked until live Teams tenant proof)
  • Maintainers: to clear the proof gate, run with a Teams channel thread with >50 replies and confirm agent context contains the most recent replies

…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]>
@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: L 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 4, 2026, 11:00 PM ET / 03:00 UTC.

Summary
The PR changes Microsoft Teams thread reply fetching to use fetchAllGraphPages, select the newest reply window by createdDateTime, and add mocked unit coverage for paginated replies.

PR surface: Source +500, Tests +374. Total +874 across 3 files.

Reproducibility: yes. Source inspection on current main shows fetchThreadReplies still performs one $top=50 replies request and the monitor handler injects that result into thread context; I did not run a live Teams tenant in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Graph Page Fanout: 1 request -> up to 50 pages. The changed helper runs on the Teams inbound reply-context path, so real Graph latency and throttling matter before merge.
  • Current-Base Drift: 3 files shown as added; 1 identical to current main. The PR's GitHub diff is against an old base, so maintainers need a rebase or refreshed mergeability before reviewing the true merge patch.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98870
Summary: This PR is an open candidate fix for the canonical Teams thread reply pagination bug; prior similar PRs are closed unmerged and remain useful context, not safe canonical replacements.

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: 🐚 platinum hermit
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:

  • [P1] Add redacted live Teams or Graph output for a channel thread with more than 50 replies showing newest replies in the final context window.
  • Refresh or rebase the branch onto current main so GitHub shows the true two-file patch and resolves mergeability.
  • Include rough latency or throttling observations for the bounded 50-page path, or get explicit maintainer acceptance of mocked-only proof.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body explicitly says no live Teams tenant proof was run; the contributor should add redacted Teams or Graph output from a real long thread and avoid exposing IPs, keys, phone numbers, tenant details, or private endpoints. 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 PR can turn one inbound Teams reply-context Graph request into as many as 50 sequential page reads, and there is no live tenant proof for latency or throttling.
  • [P1] The changed context selection depends on live Graph pagination and timestamps matching the mocked path; no redacted Teams or Graph output from a thread with more than 50 replies is provided.
  • [P1] The PR is based on an old base where these files appear as new additions; current main already has the files and GitHub mergeability is still unknown, so the branch needs a current-base refresh before maintainers judge the actual merge result.

Maintainer options:

  1. Require Live Long-Thread Proof (recommended)
    Pause merge until the PR body includes redacted Teams or Graph output from a channel thread with more than 50 replies showing newest replies in the final context window and acceptable latency.
  2. Reduce Or Guard The Page Cap
    If maintainers are uncomfortable with up to 50 Graph reads on the inbound path, lower the cap or add an approved performance guard before merge.
  3. Accept Mocked-Only Graph Risk
    Maintainers can intentionally accept the unit-tested helper path without live tenant proof, but they would own any live pagination, timestamp, or throttling mismatch.

Next step before merge

  • [P1] The remaining blockers are contributor or maintainer live proof, risk acceptance, and current-base refresh, not a narrow code repair ClawSweeper should attempt automatically.

Security
Cleared: The diff does not change secrets, permissions, package sources, CI, or code-execution surfaces; it only adds bounded authorized Graph reads through existing helpers.

Review details

Best possible solution:

Rebase the branch onto current main, keep the helper-level pagination fix, and merge only after redacted long-thread Teams/Graph proof or explicit maintainer acceptance of the bounded mocked-only risk.

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

Yes. Source inspection on current main shows fetchThreadReplies still performs one $top=50 replies request and the monitor handler injects that result into thread context; I did not run a live Teams tenant in this read-only review.

Is this the best way to solve the issue?

Yes for code shape: the narrow fix belongs in the Teams fetchThreadReplies helper and reuses the existing pagination helper. It is not merge-ready until live proof or explicit maintainer acceptance covers Graph fanout and the branch is refreshed against current main.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4d181b62a96d.

Label changes

Label changes:

  • add P2: This is a focused Teams stale-context bugfix with limited blast radius but real context quality impact for long threads.
  • add merge-risk: 🚨 session-state: The PR changes which Teams thread replies enter agent context, and live Graph pagination behavior is not proven beyond mocks.
  • add merge-risk: 🚨 availability: The inbound reply-context path can now make up to 50 sequential Microsoft Graph page requests for one message.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🐚 platinum hermit.
  • add 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 explicitly says no live Teams tenant proof was run; the contributor should add redacted Teams or Graph output from a real long thread and avoid exposing IPs, keys, phone numbers, tenant details, or private endpoints. 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.

Label justifications:

  • P2: This is a focused Teams stale-context bugfix with limited blast radius but real context quality impact for long threads.
  • merge-risk: 🚨 session-state: The PR changes which Teams thread replies enter agent context, and live Graph pagination behavior is not proven beyond mocks.
  • merge-risk: 🚨 availability: The inbound reply-context path can now make up to 50 sequential Microsoft Graph page requests for one message.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🐚 platinum hermit.
  • 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 explicitly says no live Teams tenant proof was run; the contributor should add redacted Teams or Graph output from a real long thread and avoid exposing IPs, keys, phone numbers, tenant details, or private endpoints. 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 +500, Tests +374. Total +874 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 500 0 +500
Tests 1 374 0 +374
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 874 0 +874

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, and also authored the shared Graph pagination-helper work. (role: introduced behavior and adjacent feature owner; confidence: high; commits: 8c852d86f759, 355794c24a39; files: extensions/msteams/src/graph-thread.ts, extensions/msteams/src/graph-thread.test.ts, extensions/msteams/src/graph.ts)
  • BradGroux: Merged the original thread-history and pagination-helper PRs, and review history shows they specifically raised the oldest-first reply-window concern on the thread-history PR. (role: reviewer, merger, and adjacent contributor; confidence: high; commits: 8c852d86f759, 355794c24a39; files: extensions/msteams/src/graph-thread.ts, extensions/msteams/src/graph.ts, extensions/msteams/src/monitor-handler/message-handler.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.

@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 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Added note below on proof limitations and the reuse of fetchAllGraphPages which is already used in production by listChannelsForTeam and listTeamsByName:

Proof rationale: This change does not introduce new Graph API interactions — it reuses fetchAllGraphPages, which is already used in production for listChannelsForTeam and listTeamsByName. The helper follows @odata.nextLink with a 50-page cap. The only change is calling this existing, proven helper with the replies path instead of a single fetchGraphJson call.

Unit test coverage:

  • Paginated path: 60 replies across 2 pages → newest 50 returned (chronological)
  • Non-paginated path: 30 replies → all 30 returned unchanged
  • All existing tests for path/limits/empty pass unchanged

L1 limitation: No live Teams tenant available. The createdDateTime sort is the canonical approach for selecting newest items from oldest-first data.

@clawsweeper

clawsweeper Bot commented Jul 5, 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.

@LiuwqGit

LiuwqGit commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Closing and replacing with rebased version — merging upstream main to resolve conflicts and bring the new graph.ts/graph-thread.ts from main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams 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: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence.

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