Skip to content

fix(msteams): re-land revoked-context proactive fallback#59937

Closed
BradGroux wants to merge 1 commit intoopenclaw:mainfrom
BradGroux:bgod/reland-27224-revoked-fallback
Closed

fix(msteams): re-land revoked-context proactive fallback#59937
BradGroux wants to merge 1 commit intoopenclaw:mainfrom
BradGroux:bgod/reland-27224-revoked-fallback

Conversation

@BradGroux
Copy link
Copy Markdown
Contributor

Summary

Re-lands the revoked-context proactive fallback from #27224 (originally by @openperf, merged by @steipete on Mar 2) which was lost during a subsequent force-push to main.

This is a prerequisite for #55198 (channel reply threading fix).

Problem

When a thread reply's turn context is revoked (e.g. due to Bot Framework message debouncing), all remaining messages in the batch are silently lost. The original fix landed in #27224 but the merge commit (089a878) is no longer on main.

Changes

  • messenger.ts: Extract sendMessagesInContext and sendProactively helpers from inline code. Thread path now uses per-message try/catch with isRevokedProxyError detection — on revocation, remaining messages fall through to the proactive delivery path.
  • errors.ts: Add isRevokedProxyError() utility (also lost in the refactor).
  • messenger.test.ts: Add test coverage for both full revocation (all messages) and partial revocation (first succeeds, rest fall back). Add shared createRecordedSendActivity test helper.
  • CHANGELOG.md: Entry under Unreleased > Fixes.

Testing

  • Existing tests pass (pre-existing getOAuthProviders import chain issue is unrelated — reproduces on main without this PR)
  • New tests cover: full context revocation fallback, partial revocation with per-message granularity

Related

Copilot AI review requested due to automatic review settings April 2, 2026 22:45
@BradGroux BradGroux added the channel: msteams Channel integration: msteams label Apr 2, 2026
@openclaw-barnacle openclaw-barnacle bot added size: S maintainer Maintainer-authored PR labels Apr 2, 2026
@BradGroux BradGroux self-assigned this Apr 2, 2026
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

Re-lands the Microsoft Teams “revoked turn context” proactive fallback so batched thread replies are not silently dropped when the Bot Framework turn context proxy is revoked (e.g., via message debouncing).

Changes:

  • Refactors sendMSTeamsMessages to extract sendMessagesInContext / sendProactively, and adds per-message revoked-context detection to fall back to proactive sends for remaining messages.
  • Reintroduces isRevokedProxyError() to detect revoked proxy failures.
  • Adds unit tests covering full and partial revoked-context fallback behavior, plus a shared createRecordedSendActivity helper.
  • Adds an Unreleased changelog entry documenting the fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
extensions/msteams/src/messenger.ts Adds per-message revoked-context fallback from thread sends to proactive sends; extracts helper functions.
extensions/msteams/src/errors.ts Adds isRevokedProxyError() utility used by the fallback logic.
extensions/msteams/src/messenger.test.ts Adds regression tests for full/partial revocation and a shared send-activity recorder helper.
CHANGELOG.md Documents the fix under Unreleased > Fixes.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR re-lands the revoked-context proactive fallback for MS Teams (#27224) that was lost in a subsequent force-push to main. It refactors the thread-send path in messenger.ts into two reusable helpers (sendMessagesInContext / sendProactively) and wraps each per-message send in a try/catch that detects a revoked-proxy TypeError and falls through to proactive delivery for that message and all remaining ones. A new isRevokedProxyError() utility is added to errors.ts and two targeted integration tests are added.

Key observations:

  • Core logic is correct: when a revoked proxy error is caught at index idx, messages.slice(idx) correctly includes the failing message so nothing is lost, and non-revocation errors still propagate normally.
  • The retry loop in sendWithRetry will not inadvertently retry a revoked-proxy TypeError because classifyMSTeamsSendError classifies it as "unknown" and shouldRetry returns false for that kind — the error surfaces immediately to the outer handler as expected.
  • The proactive fallback strips activityId from the conversation reference, sending fallback messages to the conversation root rather than as thread replies, which is the best available behavior when the thread context is gone.
  • Minor: the if (remaining.length > 0) guard (line 491) is always true by loop invariant and is effectively dead code.
  • isRevokedProxyError has no dedicated unit tests in errors.test.ts; it is only exercised through the new integration tests.

Confidence Score: 4/5

Safe to merge — the re-landed logic is correct and well-tested; only minor style nits remain.

The core fallback logic is sound: per-message try/catch, correct slice semantics (failing message is included in the proactive batch), non-revocation errors still propagate, and retry does not fire for revoked-proxy TypeErrors. Two new integration tests cover both full and partial revocation. Small deductions for the always-true remaining.length > 0 guard (dead code) and the absence of dedicated unit tests for isRevokedProxyError in errors.test.ts.

No files require special attention; the two style items are non-blocking.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/messenger.ts
Line: 490-493

Comment:
**Redundant guard — `remaining` is always non-empty here**

`messages.slice(idx)` always includes at least `messages[idx]` (the currently-failing message), so `remaining.length > 0` is always `true` at this point. The `if` branch is dead code and can be removed to avoid confusion for future readers.

```suggestion
        messageIds.push(...(await sendProactively(remaining, idx)));
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/msteams/src/errors.ts
Line: 177-182

Comment:
**No dedicated unit tests for `isRevokedProxyError`**

`isRevokedProxyError` is only exercised indirectly through the `messenger.test.ts` integration tests. Consider adding a small unit test block in `errors.test.ts` covering:
- non-`TypeError` values (plain `Error`, a string, `null`) → should return `false`
- a `TypeError` with an unrelated message → should return `false`
- a `TypeError` with the canonical revoked-proxy message → should return `true`

This makes the utility's contract explicit and guards against future regressions to the regex independently of the messenger layer.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(msteams): re-land revoked-context pr..." | Re-trigger Greptile

Comment on lines +490 to +493
const remaining = messages.slice(idx);
if (remaining.length > 0) {
messageIds.push(...(await sendProactively(remaining, idx)));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant guard — remaining is always non-empty here

messages.slice(idx) always includes at least messages[idx] (the currently-failing message), so remaining.length > 0 is always true at this point. The if branch is dead code and can be removed to avoid confusion for future readers.

Suggested change
const remaining = messages.slice(idx);
if (remaining.length > 0) {
messageIds.push(...(await sendProactively(remaining, idx)));
}
messageIds.push(...(await sendProactively(remaining, idx)));
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/messenger.ts
Line: 490-493

Comment:
**Redundant guard — `remaining` is always non-empty here**

`messages.slice(idx)` always includes at least `messages[idx]` (the currently-failing message), so `remaining.length > 0` is always `true` at this point. The `if` branch is dead code and can be removed to avoid confusion for future readers.

```suggestion
        messageIds.push(...(await sendProactively(remaining, idx)));
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +177 to +182
export function isRevokedProxyError(err: unknown): boolean {
if (!(err instanceof TypeError)) {
return false;
}
return /proxy that has been revoked/i.test(err.message);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No dedicated unit tests for isRevokedProxyError

isRevokedProxyError is only exercised indirectly through the messenger.test.ts integration tests. Consider adding a small unit test block in errors.test.ts covering:

  • non-TypeError values (plain Error, a string, null) → should return false
  • a TypeError with an unrelated message → should return false
  • a TypeError with the canonical revoked-proxy message → should return true

This makes the utility's contract explicit and guards against future regressions to the regex independently of the messenger layer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/errors.ts
Line: 177-182

Comment:
**No dedicated unit tests for `isRevokedProxyError`**

`isRevokedProxyError` is only exercised indirectly through the `messenger.test.ts` integration tests. Consider adding a small unit test block in `errors.test.ts` covering:
- non-`TypeError` values (plain `Error`, a string, `null`) → should return `false`
- a `TypeError` with an unrelated message → should return `false`
- a `TypeError` with the canonical revoked-proxy message → should return `true`

This makes the utility's contract explicit and guards against future regressions to the regex independently of the messenger layer.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

)

Re-lands the revoked-context fallback from openclaw#27224 (originally by @openperf,
merged by @steipete) which was lost during a force-push to main.

When a thread reply's turn context is revoked (e.g. due to message debouncing),
falls back to proactive messaging per-message so remaining replies still
reach the user instead of being silently lost.

Changes:
- Extract sendMessagesInContext and sendProactively helpers
- Per-message try/catch in thread path with revoke detection
- Remaining messages fall through to proactive path on revocation
- Add isRevokedProxyError to errors.ts (also lost in refactor)
- Add test coverage for full and partial revocation scenarios

Co-authored-by: openperf <[email protected]>
@BradGroux BradGroux force-pushed the bgod/reland-27224-revoked-fallback branch from cefd93b to 9841c67 Compare April 2, 2026 23:07
@BradGroux
Copy link
Copy Markdown
Contributor Author

Closing — the revoked-context fallback from #27224 was already re-landed upstream as part of 866bd91 (refactor: harden msteams lifecycle and attachment flows), which extracted withRevokedProxyFallback into revoked-context.ts. The functionality is already on main.

Our fork was behind upstream/main, which made it look missing locally. No action needed.

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

Labels

channel: msteams Channel integration: msteams maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants