fix(slack): include bot root message in new thread sessions (#79338)#80409
Conversation
…#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes openclaw#79338.
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path. Real behavior proof Next step before merge Review detailsBest possible solution: Retry the Codex review after fixing the execution failure. Do we have a high-confidence way to reproduce the issue? Unclear. The review failed before ClawSweeper could establish a reproduction path. Is this the best way to solve the issue? Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against cac585a29f7e. |
…law#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field.
|
@bek91 Thanks for review. |
Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring openclaw#79338 has parent/root context.
|
@sxxtony Thanks for the contribution! I pushed a maintainer follow-up. The original fix was pointing in the right direction: replies to a bot-authored Slack root message need the parent/root text in context, otherwise the agent only sees changes i made:
The main reason for removing the config option is that new public config surface has a higher bar: it affects docs, schema, generated metadata, UX, long-term support, and usually needs more maintainer discussion. Since this PR can fix the reported bug without adding a new setting, the smaller behavior-only change is easier to review and safer to merge. |
…ude-root-79338 # Conflicts: # CHANGELOG.md # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]> # Conflicts: # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]> # Conflicts: # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]> # Conflicts: # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]> # Conflicts: # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]> # Conflicts: # docs/.generated/config-baseline.sha256
…80409) * fix(slack): include bot root message in new thread sessions (#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes #79338. * fix(slack): wire includeRootMessage into runtime config schema (#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring #79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]>
…#79338) (openclaw#80409) * fix(slack): include bot root message in new thread sessions (openclaw#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes openclaw#79338. * fix(slack): wire includeRootMessage into runtime config schema (openclaw#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring openclaw#79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]>
…#79338) (openclaw#80409) * fix(slack): include bot root message in new thread sessions (openclaw#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes openclaw#79338. * fix(slack): wire includeRootMessage into runtime config schema (openclaw#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring openclaw#79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]>
…#79338) (openclaw#80409) * fix(slack): include bot root message in new thread sessions (openclaw#79338) When a user replies in-thread to a bot's own message in a Slack DM, the new thread session was constructed without the parent/root message content. The agent only saw `reply_to_id` metadata and could not resolve what was being replied to, leading to confident-but-wrong actions on follow-up corrections. The thread-context resolver was filtering out every message authored by the current bot before formatting thread history, including the bot's own root message. For thread-replies starting a fresh session, that left the agent without the parent context it needed. This change retains current-bot messages in the thread history when starting a new thread session, formats them with role=assistant under a "Bot (this assistant)" sender label, and adds `channels.slack.thread.includeRootMessage` (default `true`) to opt out. Bot messages still bypass allowlist visibility filtering since the bot's own output is not third-party content. Fixes openclaw#79338. * fix(slack): wire includeRootMessage into runtime config schema (openclaw#79338) The first commit added `channels.slack.thread.includeRootMessage` to the TypeScript type and zod schema, but the runtime AJV-style schema generated from `extensions/slack/src/config-ui-hints.ts` rejected the new field with `must NOT have additional properties` at gateway boot. Adds the matching UI hint entry for `thread.includeRootMessage` and regenerates the bundled channel config metadata so the live gateway accepts the new field. * Narrow Slack thread root context handling Remove the public includeRootMessage config and keep the Slack thread fix focused on including only the current bot's root message on the first turn of a new thread session. Preserve filtering of arbitrary current-bot Slack history while ensuring openclaw#79338 has parent/root context. * Fix Slack thread root CI checks --------- Co-authored-by: Bek <[email protected]>
Summary
reply_to_idmetadata but had no way to resolve it to the actual text — leading to confident-but-wrong follow-up actions (incident Images not passed to Claude CLI - only path reference in text #4 in Slack thread sessions do not include parent/root message content #79338: a correction email sent to the wrong person about the wrong meeting).resolveSlackThreadContextDatano longer strips current-bot messages from new thread session history. The bot's own root/parent message is now retained and rendered withrole=assistantunder aBot (this assistant)sender label, so the agent has the parent text it is replying to. Adds opt-outchannels.slack.thread.includeRootMessage(defaulttrue).inheritParentsemantics are untouched.ThreadStarterBodyremains user-authored; the bot's root flows viaThreadHistoryBodyso downstream "thread starter" UX is not retroactively populated by bot output.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Real behavior proof (required for external PRs)
Behavior or issue addressed: Slack DM thread reply to a bot message constructs a new thread session that omits the parent message; the agent loses context of what it is replying to.
Real environment tested: OpenClaw 2026.5.10-beta.1 built from this branch on Ubuntu 24.04 (Linux 6.8.0-111-generic, x64), Node v22.22.0. Live gateway boot via
node dist/entry.js gatewayagainst a config that setschannels.slack.thread.includeRootMessage. Direct invocation of the builtresolveSlackThreadContextDatafunction with a synthetic Slackconversations.repliespayload to capture the runtimeThreadHistoryBodystring the agent receives.Exact steps or command run after this patch:
Test config used:
{ "channels": { "slack": { "enabled": true, "botToken": "xoxb-fake-test-only", "appToken": "xapp-1-fake-test-only", "thread": { "historyScope": "thread", "initialHistoryLimit": 20, "includeRootMessage": true } } } }Evidence after fix (live runtime output, redacted):
(1) Live gateway boot — config schema accepts the new field, gateway reaches
ready:Compare to the same boot before the schema-wiring fixup commit, which rejected the field:
(2) Direct runtime call into the built
resolveSlackThreadContextData(defaultincludeRootMessage=true, the fix):(3) Same runtime call against
main(before the fix):→ On
main,ThreadHistoryBodyis undefined. The agent receives only the user's reply text plusreply_to_idmetadata, exactly matching the failure mode in Slack thread sessions do not include parent/root message content #79338. With the fix on this branch, the bot's parent message is rendered with(assistant)role and shows up at the top ofThreadHistoryBody.(4) Opt-out path runtime call (
includeRootMessage: false):→ Operators who explicitly do not want the bot's own root message in new thread sessions get the prior behavior back.
Observed result after fix:
ThreadHistoryBodyincludes the bot's parent message labeledBot (this assistant) (assistant).ThreadLabelbecomesSlack thread <room> (assistant root): <snippet>.includeRootMessage: false: prior behavior preserved (parent omitted).must NOT have additional properties.What was not tested: A multi-tenant production Slack workspace with real
conversations.repliestraffic (the test environment does not have a Slack app with validxapp-1-…socket-mode credentials; the boot log above showsinvalid_authreaching Slack as expected for fake tokens, which is the only boundary not exercised). All in-process code paths (config validation, schema acceptance, thread context resolution, history filter policy, role labeling) are exercised live above.Before evidence: see (3) above and the original schema-rejection log for the gateway boot.
Root Cause (if applicable)
resolveSlackThreadContextData(extensions/slack/src/monitor/message-handler/prepare-thread-context.ts) filtered current-bot messages out ofthreadHistory(threadHistoryWithoutCurrentBot = threadHistory.filter(!isCurrentBotAuthor)) and also gatedthreadStarterBody/threadLabelon!starterIsCurrentBot. That prevented duplication during normal turns, where the bot's prior outputs already live in the assistant's transcript — but for thread-reply turns the new thread session has no prior transcript at all, so the bot's parent message vanished.ThreadHistoryBodyfor a bot-rooted thread; the existing tests actively verified the (incorrect) "omits bot starter" behavior.Regression Test Plan (if applicable)
extensions/slack/src/monitor/message-handler/prepare-thread-context.test.ts(new testissue #79338: …)extensions/slack/src/monitor/message-handler/prepare-thread-context-root.test.ts(26 new helper tests)extensions/slack/src/monitor/message-handler/prepare.test.ts(existing integration test updated to assert bot reply now appears inThreadHistoryBody)ThreadHistoryBodywithrole=assistantand a recognizable sender label, regardless ofcontextVisibilityMode.prepare-thread-context.tsis the single function that constructs every thread session's pre-prompt context for Slack. Asserting its outputs at this seam is cheaper and more deterministic than a full inbound→agent E2E and exercises the exact branch that broke.User-visible / Behavior Changes
channels.slack.thread.includeRootMessage(boolean, defaulttrue). Operators who explicitly do not want the bot's own root message replayed into a new thread session can set it tofalseand recover the prior behavior.ThreadHistoryBodylabeledBot (this assistant) (assistant)instead of being silently dropped.ThreadLabelfor bot-rooted thread sessions gains an(assistant root): <snippet>suffix so per-session telemetry/logs show which bot message anchored the thread.Diagram (if applicable)
Security Impact (required)
conversations.repliescall is used; only the post-fetch filter changed.channels.slack.thread.includeRootMessage: false.Repro + Verification
Environment
ThreadHistoryBody)Steps
agent:<id>:slack:direct:<userId>:thread:<threadTs>, a fresh session.Expected
The new thread session's
ThreadHistoryBodycontains the bot's parent text labeledBot (this assistant) (assistant). The agent answers the corrective reply with full context.Actual (this branch)
Matches expected. Live runtime captures above show
ThreadHistoryBodycontains the bot parent on this branch and isundefinedonmain.Evidence
Human Verification (required)
includeRootMessagefield (was rejected before the schema-wiring commit).resolveSlackThreadContextDatashows bot's parent inThreadHistoryBodyon this branch.mainshowsThreadHistoryBody: undefined— reproduces Slack thread sessions do not include parent/root message content #79338.includeRootMessage: false) at runtime restores prior behavior.resolveSlackThreadHistoryFilterPolicyunit test).userIdrather thanbotId, mixed third-party-bot + current-bot history.xapp-1-…socket-mode auth (the test environment does not have a real Slack app available). The SlackWebClient.conversations.repliesboundary is exercised through a synthetic payload identical in shape to Slack's documented response. All other code paths are exercised in-process.Compatibility / Migration
channels.slack.thread.includeRootMessage(optional, defaulttrue). Existing configs without the field get the fix automatically.Risks and Mitigations
channels.slack.thread.includeRootMessage: falseto restore prior behavior; documented indocs/channels/slack.md.(assistant)role labels and untrusted-envelope wrapping (formatInboundEnvelope), matching how the rest of the thread history is rendered.Review Conversations