fix(msteams): make resolveMSTeamsRouteSessionKey idempotent against pre-suffixed bases (#66771)#78850
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. at source level: current main mutates the Teams route session key and then passes a potentially pre-suffixed base into a helper that appends another thread suffix. The PR body adds live malformed lane-key evidence and terminal output showing the patched helper normalizes those keys. Real behavior proof Next step before merge Security Review detailsBest possible solution: Land the Teams-owned helper idempotency fix after required CI completes, while keeping broader handler or route-cache refactors tracked separately in #59294. Do we have a high-confidence way to reproduce the issue? Yes, at source level: current main mutates the Teams route session key and then passes a potentially pre-suffixed base into a helper that appends another thread suffix. The PR body adds live malformed lane-key evidence and terminal output showing the patched helper normalizes those keys. Is this the best way to solve the issue? Yes, the helper-level strip-and-append fix is the narrowest Teams-owned repair for the reported malformed lane keys without changing shared routing cache semantics. A shared cache clone fix can remain a separate follow-up if maintainers want it. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 419b6e8993e4. |
2268819 to
3268f49
Compare
|
Maintainer prep update for #78850 / #66771. What changed:
Prepared head:
Verification run locally after the final rebase:
Full
Fresh CI is now running on the prepared head. |
|
Maintainer follow-up for #78850 / #66771. ClawSweeper correctly caught that the changelog entry was under Prepared head:
Verification after rebasing onto current
Fresh CI is running on the updated prepared head. |
3268f49 to
0feaddb
Compare
…re-suffixed bases (openclaw#66771) When the inbound message handler reuses a `route.sessionKey` that was already thread-qualified by a prior turn (e.g. cached resolve-route hit, or the cache-miss return at src/routing/resolve-route.ts:696 whose object reference is then mutated in-place by message-handler.ts:489), naively appending the current thread id compounds into `…:thread:OLD:thread:NEW` and routes the turn to a malformed lane that splits same-thread context across turns. Strip any trailing `:thread:<id>+` segments from the base before delegating to resolveThreadSessionKeys. Thread ids never contain ':' so the segment boundary is unambiguous; the '+' covers pathological doubly-suffixed bases already in caches/persisted sessions from prior runs. This is defense-in-depth at the helper level, complementary to the larger handler refactor in openclaw#59294 (still open since 2026-04-01). Even after that or a routing-cache fix lands, making the helper itself idempotent prevents any future caller hygiene regression from re-introducing the bug. Adds four regression tests under a new "idempotency against pre-suffixed bases" describe block in message-handler.thread-session.test.ts: - already-thread-suffixed base + new thread → single clean suffix - doubly-thread-suffixed (already-malformed) base → collapsed to one - same-thread re-application is a fixed point - stale thread suffix + top-level inbound → bare base Closes openclaw#66771.
0feaddb to
0403c35
Compare
) Normalize pre-thread-qualified Teams route session keys before deriving channel-thread lanes so cached route reuse cannot create malformed mixed thread sessions. Co-authored-by: Harris Ali <[email protected]>
) Normalize pre-thread-qualified Teams route session keys before deriving channel-thread lanes so cached route reuse cannot create malformed mixed thread sessions. Co-authored-by: Harris Ali <[email protected]>
) Normalize pre-thread-qualified Teams route session keys before deriving channel-thread lanes so cached route reuse cannot create malformed mixed thread sessions. Co-authored-by: Harris Ali <[email protected]>
Summary
Make
resolveMSTeamsRouteSessionKeyidempotent against pre-suffixed session keys by stripping any trailing(:thread:<id>)+segments frombaseSessionKeybefore delegating toresolveThreadSessionKeys. Closes #66771.This is defense-in-depth at the helper boundary, complementary to (not a substitute for) the larger handler refactor in #59294. Even after that or a routing-cache clone-on-miss fix lands, making this helper itself idempotent prevents future caller-hygiene regressions from re-introducing the bug at any caller site.
Problem
Per #66771, msteams session lane keys can land in malformed shape:
instead of the well-formed
Same-thread inbound turns split across distinct lanes; unrelated threads can collapse onto the same malformed key under the right cache state. Observed on
@openclaw/msteams 2026.5.5.Root cause
extensions/msteams/src/monitor-handler/message-handler.ts:489reassignsroute.sessionKey = resolveMSTeamsRouteSessionKey({ baseSessionKey: route.sessionKey, … }). Therouteobject came fromcore.channel.routing.resolveAgentRoute(...). On a cache miss atsrc/routing/resolve-route.ts:680-696, the resolver creates the route, stores it inrouteCache.set(routeCacheKey, route), and returns the same object reference (line 696:return route;, no clone). The cache hit path at line 650 returns{ ...cachedRoute }(shallow clone) — but a shallow clone of an already-mutated cached object still carries the mutatedsessionKey. So once a turn writesroute.sessionKey = "…:thread:A"after a miss, the cached reference now has that suffix; subsequent hits return shallow copies that still include it;resolveMSTeamsRouteSessionKeythen appends the new thread id on top, producing the mixed key.The helper currently has no defense — it forwards
params.baseSessionKeystraight intoresolveThreadSessionKeys(extensions/msteams/src/monitor-handler/thread-session.ts:12), which appends without any pre-existing-suffix check (src/routing/session-key.ts:251-253).Approach
Three plausible fault domains; this PR takes (3):
route.sessionKeyin the handlersrc/routing/, broader review weight.Approach (3) doesn't preempt either of the other fixes — it's defensive at a different layer. After #59294 or a cache-clone fix lands, the helper still being idempotent is cheap insurance against future regressions at any caller of
resolveMSTeamsRouteSessionKey.Change
Thread ids in Teams are timestamps/uuids — never contain
:— so the segment boundary in the regex is unambiguous. The+covers pathological doubly-suffixed keys that may already exist in caches/persisted sessions from prior runs.Tests
Adds four regression cases under a new
idempotency against pre-suffixed bases (#66771)describe block inmessage-handler.thread-session.test.ts::thread:<new>suffix.All existing tests pass unchanged. (Unit test runs are supplementary per the maintainers' proof gate; live behavioral evidence below is the load-bearing artifact.)
Real behavior proof
:thread:OLD:thread:NEWor duplicate:thread:X:thread:Xsuffixes when a previously suffixed route session key was passed back through the Teams helper. This patch strips stale trailing Teams thread suffixes before appending the current thread id.@openclaw/msteams2026.5.5 patched locally with this helper change, Microsoft Teams Bot Framework channel-thread inbound activities in a Posts-style channel.~/.openclaw/agents/main/sessions/sessions.jsonlane store, patched the local@openclaw/msteamsbundle with this PR's helper change, restarted the gateway, confirmed the patched bundle was loaded, then rannode /tmp/verify-66771.mjsagainst the exact malformed lane keys captured from the live runtime store.sessions.jsonstore after loading this helper patch into the local msteams bundle. Sharedsrc/routing/resolve-route.tscache cloning and the larger handler mutation refactor in fix(msteams): isolate thread sessions, outbound targeting, and attachment resolution #59294 are intentionally out of scope for this helper-level fix.Scope and risks
thread-session.ts, +14 / -2).message-handler.thread-session.test.ts, +55 / 0).src/routing/surface or anyCODEOWNERS-restricted file.Checklist
CODEOWNERS-restricted files