fix(slack): restore persistent per-channel session routing#32320
fix(slack): restore persistent per-channel session routing#32320steipete merged 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR correctly fixes a regression introduced in commit
Confidence Score: 5/5
Last reviewed commit: 7e469fd |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e469fd0cb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const roomThreadId = isThreadReply && threadTs ? threadTs : undefined; | ||
| const canonicalThreadId = isRoomish ? roomThreadId : isThreadReply ? threadTs : autoThreadId; |
There was a problem hiding this comment.
Keep thread starters on the same session as thread replies
This change makes every top-level room message use the channel session key, so in replyToMode="all" the thread starter turn is stored under agent:...:slack:channel:<id> while the very next user reply in that same thread switches to :thread:<thread_ts>. In configurations that set channels.slack.thread.initialHistoryLimit to 0 (see src/slack/monitor/message-handler/prepare-thread-context.ts:73-80), the new thread session cannot backfill prior thread messages, so the follow-up loses the assistant context from the opener that previously lived in the same session.
Useful? React with 👍 / 👎.
| it("keeps top-level channel messages on the per-channel session regardless of replyToMode", async () => { | ||
| for (const mode of ["all", "first", "off"] as const) { | ||
| const ctx = buildCtx({ replyToMode: mode }); | ||
| ctx.resolveUserName = async () => ({ name: "Carol" }); | ||
| const account = createSlackTestAccount({ replyToMode: mode }); | ||
|
|
||
| const first = await prepareSlackMessage({ | ||
| ctx, | ||
| account, | ||
| message: buildChannelMessage({ ts: "1770408530.000000" }), | ||
| opts: { source: "message" }, | ||
| }); | ||
| const second = await prepareSlackMessage({ | ||
| ctx, | ||
| account, | ||
| message: buildChannelMessage({ ts: "1770408531.000000" }), | ||
| opts: { source: "message" }, | ||
| }); | ||
|
|
||
| expect(first).toBeTruthy(); | ||
| expect(second).toBeTruthy(); | ||
| const firstKey = first!.ctxPayload.SessionKey as string; | ||
| const secondKey = second!.ctxPayload.SessionKey as string; | ||
| expect(firstKey).toBe(secondKey); | ||
| expect(firstKey).not.toContain(":thread:"); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Loop-based test obscures which replyToMode value failed
The three modes are exercised inside a single it block with a for loop. If the assertion fails for, say, "first", the error output won't immediately surface the offending mode — you'd need to dig into the stack trace. Consider using separate it blocks per mode (or dynamically generating them via it.each) so that CI output clearly identifies which mode regressed.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/slack/monitor/message-handler/prepare.thread-session-key.test.ts
Line: 86-112
Comment:
**Loop-based test obscures which `replyToMode` value failed**
The three modes are exercised inside a single `it` block with a `for` loop. If the assertion fails for, say, `"first"`, the error output won't immediately surface the offending mode — you'd need to dig into the stack trace. Consider using separate `it` blocks per mode (or dynamically generating them via `it.each`) so that CI output clearly identifies which mode regressed.
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!
Additional Comments (1)
The three-line comment block immediately above Consider splitting the comment so each variable is described individually, e.g.: Prompt To Fix With AIThis is a comment left during a code review.
Path: src/slack/monitor/message-handler/prepare.ts
Line: 281-287
Comment:
**Stale comment now describes `roomThreadId`, not `autoThreadId`**
The three-line comment block immediately above `autoThreadId` was originally written to capture the combined intent of `autoThreadId` *and* the old `roomThreadId` chain. After this PR, the first two lines — *"Keep true thread replies thread-scoped, but preserve channel-level sessions for top-level room turns when replyToMode is off"* — describe the new `roomThreadId` logic, not `autoThreadId` (which is exclusively used for DMs). Leaving this comment attached to `autoThreadId` will confuse future readers about what that variable actually controls.
Consider splitting the comment so each variable is described individually, e.g.:
```suggestion
// For DMs with replyToMode="all", create a per-message auto-thread so each
// top-level DM turn gets its own isolated session.
const autoThreadId =
!isThreadReply && replyToMode === "all" && threadContext.messageTs
? threadContext.messageTs
: undefined;
```
How can I resolve this? If you propose a fix, please make it concise. |
Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
7e469fd to
d0aee39
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0aee39807
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Top-level channel messages must stay on the per-channel session for continuity. | ||
| // Before this fix, every channel message used its own ts as threadId, creating | ||
| // isolated sessions per message (regression from #10686). | ||
| const roomThreadId = isThreadReply && threadTs ? threadTs : undefined; |
There was a problem hiding this comment.
Start inherited thread sessions on the starter message
With channels.slack.thread.inheritParent=true, this change makes top-level channel messages skip thread session creation (roomThreadId is now undefined unless it is already a thread reply), so the thread session is created later on the first reply. At that point, ParentSessionKey forking in auto-reply/reply/session.ts copies the current channel transcript, which can include unrelated messages posted after the thread started, causing cross-thread context leakage in busy channels; previously the fork happened on the starter turn and used a stable snapshot.
Useful? React with 👍 / 👎.
|
Landed.
Thanks @AytuncYildizli! |
…2353) Adds explicit test coverage for the edge case where Slack auto-sets thread_ts == ts on top-level messages. This was fixed in openclaw#32320 but lacked a specific regression test for this exact scenario. The test verifies that messages with thread_ts == ts do NOT get a :thread: suffix in their session key, ensuring they stay on the per-channel session for continuity. Related: openclaw#32320, openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285 (cherry picked from commit a448435)
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285 (cherry picked from commit a448435)
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
…32320) Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off. Introduced in openclaw#10686, every new channel message got its own session key (agent:...:thread:<messageTs>), breaking conversation continuity. Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode. Fixes openclaw#32285
Fixes #32285
Problem
After upgrading from v2026.2.26 to v2026.3.1, Slack channel messages create isolated sessions instead of persistent per-channel sessions. Each message starts fresh with no conversation context. Downgrading to v2026.2.26 restores expected behavior.
Root Cause
Commit 11d3470 (
fix(slack): use thread-level sessions for channels to prevent context mixing (#10686)) introduced per-message thread scoping for channel messages. TheroomThreadIdcomputation fell through tothreadContext.messageTs(the message's own timestamp) wheneverreplyToModewas not"off", giving every top-level channel message its own session key (agent:…:thread:<messageTs>).This effectively isolated every single message into its own session, breaking conversation continuity.
Fix
Only derive thread-specific session keys when the message is an actual thread reply (
thread_tspresent and different from the message's ownts). Top-level channel messages now stay on the per-channel session key (agent:main:slack:channel:<channelId>) regardless ofreplyToModesetting.Thread replies continue to get their own sessions via the parent
thread_ts, preserving the cross-thread isolation that #10686 intended.Changes
src/slack/monitor/message-handler/prepare.ts: SimplifiedroomThreadIdto only usethreadTsfor actual thread repliessrc/slack/monitor/message-handler/prepare.thread-session-key.test.ts: Updated tests to assert per-channel session continuity for allreplyToModevaluesTesting