openclaw - ✅(Solved) Fix Slack: outbound thread reply does not promote the session key to the thread, so the next inbound thread message lands in a fresh session [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#78505Fetched 2026-05-07 03:36:08
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
2
Timeline (top)
mentioned ×2subscribed ×2commented ×1cross-referenced ×1

When a user sends a top-level message in a Slack channel, OpenClaw creates a channel-level session and the bot replies inside a thread under that message. The reply lives visibly inside a Slack thread, but the OpenClaw session is not promoted to a thread-scoped session.

When the user then replies inside that same thread, OpenClaw does not match it back to the original session. It opens a brand new thread-scoped session and all subsequent messages route there, with no link to the turn that started the conversation.

So the asymmetry is:

  • Inbound (top-level message) → channel session, with bot delivery happening in a Slack thread.
  • Outbound (bot reply) → session key stays at channel scope, even though the reply created a thread.
  • Inbound (user replies in that thread) → session key resolves to a new thread-scoped session, disconnected from the original.

In older OpenClaw versions this worked correctly: either the session stayed at channel scope on both sides, or the session got promoted to thread scope at delivery time so the next inbound thread message landed in the same session.

Root Cause

When a user sends a top-level message in a Slack channel, OpenClaw creates a channel-level session and the bot replies inside a thread under that message. The reply lives visibly inside a Slack thread, but the OpenClaw session is not promoted to a thread-scoped session.

When the user then replies inside that same thread, OpenClaw does not match it back to the original session. It opens a brand new thread-scoped session and all subsequent messages route there, with no link to the turn that started the conversation.

So the asymmetry is:

  • Inbound (top-level message) → channel session, with bot delivery happening in a Slack thread.
  • Outbound (bot reply) → session key stays at channel scope, even though the reply created a thread.
  • Inbound (user replies in that thread) → session key resolves to a new thread-scoped session, disconnected from the original.

In older OpenClaw versions this worked correctly: either the session stayed at channel scope on both sides, or the session got promoted to thread scope at delivery time so the next inbound thread message landed in the same session.

Fix Action

Fixed

PR fix notes

PR #78522: fix(slack): seed thread routing for implicit-conversation channels (#78505)

Description (problem / solution / changelog)

Summary

  • Problem: In Slack channels with requireMention: false, the inbound root turn landed on the channel session (agent:main:slack:channel:<id>) while later replies inside the bot-created thread landed on a fresh :thread:<root_ts> session, splitting one logical conversation across two OpenClaw sessions.
  • Why it matters: The bot loses continuity with the message it just replied to as soon as the user enters the thread; session list also bloats with one extra session per Slack thread. Reported in #78505.
  • What changed: Extend seedTopLevelRoomThreadBySource in prepareSlackMessage to also fire when the channel will implicitly produce a Slack thread anyway (isRoom + requireMention: false + replyToMode != "off"). The root and its thread replies now share the same :thread:<root_ts> session key, the same way app_mention / explicit-mention roots already do.
  • What did NOT change (scope boundary): DMs, group DMs (MPIM), requireMention: true channels, replyToMode: "off" channels, app_mention / explicit-mention paths, regex mention paths, runtime conversation bindings. No new config field, no migration. Outbound routing precedence (replyToId vs threadId) is also intentionally untouched and tracked separately.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #78505
  • Related #72498 (the seeding rule introduced there did not cover implicit-conversation channels)
  • This PR fixes a bug or regression

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: Slack thread replies in requireMention: false channels split off into a separate OpenClaw session.

  • Real environment tested: OpenClaw 2026.5.5 (production install on macOS 26.3.1 / arm64), Slack Socket Mode, channel C0AGG76CP1S with requireMention: false, channels.slack.replyToMode = "first", messages.groupChat.visibleReplies = "automatic". Local hotfix (functionally identical to this patch) applied directly to the bundled dist/prepare-CD7Ym2Zf.js and the gateway restarted via openclaw gateway restart.

  • Exact steps or command run after this patch:

    1. Send a top-level message in #genai (no @mention).
    2. The bot replies inside the thread Slack creates under that message.
    3. Reply inside that same thread.
    4. Verify with openclaw status and the sessions list snapshot.
  • Evidence after fix (live terminal output, copied verbatim from the running gateway):

Gateway health after applying the source change and restarting:

$ openclaw status
│ Gateway              │ local · ws://127.0.0.1:18789 (local loopback) · reachable 33ms · auth token · …  │
│ Gateway service      │ LaunchAgent installed · loaded · running (pid 5029, state active)                │
│ Sessions             │ 7 active · default gpt-5.5 (200k ctx) · 2 stores                                  │

Live Slack sessions for channel C0AGG76CP1S (#genai) after the fix, captured via the OpenClaw runtime sessions API and copied verbatim:

agent:main:slack:channel:c0agg76cp1s:thread:1778073105.769279     ← original turn (root + thread reply share this session)
agent:main:slack:channel:c0agg76cp1s:thread:1778079873.776589     ← brand new top-level turn (also seeded as a thread session from the start)

Notably, no bare agent:main:slack:channel:c0agg76cp1s session is created for new top-level turns anymore — both root and thread reply land on the same :thread:<root_ts> key.

Redacted runtime log excerpt from the same machine after the patch (gateway pid 5029, Slack Socket Mode connected):

[gateway] restart applied (SIGUSR1) — pid=5029 mode=emit
[slack] account=default  channel=C0AGG76CP1S  inbound  source=message  isRoom=true  requireMention=false  replyToMode=first
[slack] seeding top-level room thread (implicit-conversation channel) → routedThreadId=<root_ts>
[slack] outbound delivery → channel=C0AGG76CP1S  thread_ts=<root_ts>  ok
[slack] inbound thread reply  thread_ts=<root_ts>  → session=agent:main:slack:channel:c0agg76cp1s:thread:<root_ts>  (same session as root)
  • Observed result after fix: Each top-level message in the #genai channel routes to a :thread:<root_ts>-scoped OpenClaw session from the very first turn. Follow-up replies inside the same Slack thread resolve to the same session key as the root (no more channel-vs-thread split). Verified live via openclaw status (gateway healthy, pid 5029) and the sessions list snapshot (terminal output above), captured immediately after restarting the gateway with the patched build. No new bare agent:main:slack:channel:c0agg76cp1s session was created for any new top-level turn during the verification window.
  • What was not tested: replyToMode = "batched" channels (logic identical, just no live channel handy); ACP-bound conversations (covered by existing tests).
  • Before evidence (optional): Same install, same channel, same scenario before the patch produced two distinct OpenClaw sessions per logical conversation: a bare agent:main:slack:channel:c0agg76cp1s session for the root + a fresh agent:main:slack:channel:c0agg76cp1s:thread:<root_ts> session for every later in-thread message (live observation captured in #78505).

Root Cause (if applicable)

  • Root cause: prepareSlackMessage computed seedTopLevelRoomThreadBySource from app_mention | wasMentioned | explicitlyMentioned. Channels configured with requireMention: false accept implicit prompts (no @mention required), but the seeding rule did not include that case. The root turn therefore did not seed a thread session, while the user's later in-thread replies — being actual thread_ts replies — did get a thread-scoped session, producing the asymmetry reported in #78505.
  • Missing detection / guardrail: No regression test exercised the requireMention: false + non-off replyToMode path through prepareSlackMessage end-to-end. Existing thread-session-key tests at the routing level only fed seedTopLevelRoomThread directly, bypassing the gate where the regression actually lives.
  • Contributing context: #72498 introduced the seeding mechanism for actionable mentions but didn't generalize it to implicit-conversation channels, presumably because that path is less common in mention-gated workspaces.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/slack/src/monitor/message-handler/prepare.test.ts
  • Scenario the test should lock in: A top-level requireMention: false channel turn with replyToMode: "first" and no @mention must produce a :thread:<root_ts> session key. A subsequent thread_ts-bearing reply must resolve to the same session key.
  • Why this is the smallest reliable guardrail: The bug lives in the prepareSlackMessage seeding gate, so only an end-to-end prepareSlackMessage assertion (not a pure routing-layer one) actually pins it. Reverting the source change locally makes the new test fail with the exact mismatch from #78505 (agent:main:slack:channel:c0agg76cp1s vs …:thread:1778073105.769279), confirming the test catches the regression.
  • Existing test that already covers this: None. The closest, "keeps a root app mention and URL-only Slack thread follow-up on one parent session", only covers the app_mention path.
  • If no new test is added, why not: N/A — a new regression test is added.

User-visible / Behavior Changes

For Slack channels with requireMention: false and a non-off replyToMode, top-level inbound messages now route to a thread-scoped session (agent:<agent>:slack:channel:<id>:thread:<root_ts>) from the first turn, instead of the bare channel session. This matches what those channels already do for app_mention / explicit-mention roots, and matches the visible Slack behavior (every top-level bot reply already lives inside a Slack thread on those channels).

Channels with requireMention: true, channels with replyToMode: "off", DMs, and group DMs are unchanged.

Diagram (if applicable)

Before (requireMention: false, replyToMode: "first"):
  User: top-level message in #channel
    -> session: agent:main:slack:channel:<id>
  Bot: replies inside Slack thread under user message
  User: replies in that thread
    -> session: agent:main:slack:channel:<id>:thread:<root_ts>   ← split

After:
  User: top-level message in #channel
    -> session: agent:main:slack:channel:<id>:thread:<root_ts>   ← seeded
  Bot: replies inside that thread
  User: replies in that thread
    -> session: agent:main:slack:channel:<id>:thread:<root_ts>   ← same session

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS 26.3.1 (arm64)
  • Runtime/container: OpenClaw 2026.5.5, Node 25.8.2
  • Model/provider: gpt-5.5 (irrelevant to the routing layer)
  • Integration/channel: Slack Socket Mode
  • Relevant config (redacted):
    • messages.groupChat.visibleReplies: "automatic"
    • channels.slack.replyToMode: "first"
    • channels.slack.replyToModeByChatType: { direct: "off", group: "first", channel: "first" }
    • channels.slack.thread: { historyScope: "thread", inheritParent: false }
    • channels.slack.channels.<channelId>: { enabled: true, requireMention: false }

Steps

  1. Send a top-level message in the configured channel (no @bot).
  2. Bot replies inside the auto-created Slack thread.
  3. Reply inside that thread.
  4. Inspect openclaw status / sessions list for the channel.

Expected

  • One OpenClaw session per Slack thread, thread-scoped from the root.
  • Follow-up replies inside the thread share that session.

Actual (with fix)

  • Matches expected: both turns route to agent:main:slack:channel:<id>:thread:<root_ts>.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets (see Real behavior proof above)
$ pnpm test extensions/slack/src/monitor/message-handler/prepare.test.ts \
              extensions/slack/src/monitor/message-handler/prepare.thread-session-key.test.ts
… 65 tests passed (55 + 10) — including the new regression test.

$ pnpm test extensions/slack
… 925 tests passed (91 files).

Regression confirmation:
  - With source change reverted, the new test fails with:
      AssertionError: expected 'agent:main:slack:channel:c0agg76cp1s'
      to be 'agent:main:slack:channel:c0agg76cp1s:thread:1778073105.769279'
  - With source change in place, the test passes.

Human Verification (required)

  • Verified scenarios (live Slack):
    • Top-level message + same-thread reply in a requireMention: false channel — both turns now share one :thread:<root_ts> session.
    • Existing app_mention and explicit-mention routes — still consolidate as before (existing tests).
    • DM and MPIM routing — unchanged (existing tests).
  • Edge cases checked:
    • replyToMode: "off" channels — gate stays disabled (existing test keeps top-level channel turns in one session when replyToMode=off still passes).
    • requireMention: true channels — gate stays disabled.
  • What I did not verify in a live environment:
    • replyToMode: "batched" (logic-equivalent, covered by unit tests).
    • Multi-account Slack setups (no second workspace available locally).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Existing channels relying on the bare agent:<id>:slack:channel:<id> session for cross-thread continuity in a requireMention: false channel will now route per-thread.
    • Mitigation: This was already the de-facto behavior for app_mention channels post-#72498, and the existing pre-#72498 behavior was already inconsistent with how the bot visibly replies (inside a thread). No persisted state is migrated; existing sessions are not renamed or merged. Users wanting channel-scoped sessions can set replyToMode: "off".

Changed files

  • extensions/slack/src/monitor/message-handler/prepare.test.ts (modified, +74/-0)
  • extensions/slack/src/monitor/message-handler/prepare.ts (modified, +20/-1)

Code Example

agent:main:slack:channel:<channelId>                       ← initial turn (visible in Slack as a thread reply)
agent:main:slack:channel:<channelId>:thread:<thread_ts>    ← every subsequent thread message
RAW_BUFFERClick to expand / collapse

Summary

When a user sends a top-level message in a Slack channel, OpenClaw creates a channel-level session and the bot replies inside a thread under that message. The reply lives visibly inside a Slack thread, but the OpenClaw session is not promoted to a thread-scoped session.

When the user then replies inside that same thread, OpenClaw does not match it back to the original session. It opens a brand new thread-scoped session and all subsequent messages route there, with no link to the turn that started the conversation.

So the asymmetry is:

  • Inbound (top-level message) → channel session, with bot delivery happening in a Slack thread.
  • Outbound (bot reply) → session key stays at channel scope, even though the reply created a thread.
  • Inbound (user replies in that thread) → session key resolves to a new thread-scoped session, disconnected from the original.

In older OpenClaw versions this worked correctly: either the session stayed at channel scope on both sides, or the session got promoted to thread scope at delivery time so the next inbound thread message landed in the same session.

Environment

  • OpenClaw 2026.5.5 (npm latest)
  • Channel: Slack (Socket Mode)
  • Config:
    • messages.groupChat.visibleReplies = "automatic"
    • channels.slack.replyToMode = "first"
    • channels.slack.replyToModeByChatType = { direct: "off", group: "first", channel: "first" }
    • channels.slack.thread = { historyScope: "thread", inheritParent: false }
  • No bindings[] for the affected channel.

Reproduce

  1. In a Slack channel where the bot is active, send a top-level message that mentions the bot.
    • OpenClaw opens session: agent:main:slack:channel:<channelId>
    • Bot replies in Slack inside a thread under that message.
    • Session key remains agent:main:slack:channel:<channelId> (no thread suffix).
  2. Reply to the bot inside that thread.
    • OpenClaw opens a new session: agent:main:slack:channel:<channelId>:thread:<thread_ts>
    • All further messages in that thread route to this new session.
  3. Inspect openclaw status / sessions list.

Actual

Two distinct OpenClaw sessions for what is, from the user's perspective, one continuous conversation:

agent:main:slack:channel:<channelId>                       ← initial turn (visible in Slack as a thread reply)
agent:main:slack:channel:<channelId>:thread:<thread_ts>    ← every subsequent thread message

The bot has no continuity with the message that started the thread.

With messages.groupChat.visibleReplies = "message_tool", additional per-message sessions can also appear inside the same thread.

Expected

When OpenClaw replies inside a Slack thread, the session and the Slack thread should stay in sync, so that subsequent inbound messages in that thread route to the same session as the original turn.

That can mean either:

  • the session stays at channel scope on both sides (older default), or
  • the session gets promoted to a thread-scoped session at delivery time and the next inbound thread message resolves to that same session.

The current behavior — channel-scoped on the way out, thread-scoped on the way back in — is what causes the discontinuity.

Impact

  • The bot has no memory of the message it just replied to as soon as the user replies inside the thread.
  • Session list bloat — every Slack thread leaves behind a separate OpenClaw session.
  • Channel context is fragmented across many sessions.
  • Worse with messages.groupChat.visibleReplies = "message_tool", where individual assistant replies can create their own per-message sessions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Slack: outbound thread reply does not promote the session key to the thread, so the next inbound thread message lands in a fresh session [1 pull requests, 1 comments, 2 participants]