Skip to content

fix:Slack DM delivery mirrors can split sessions by routing bare D... IM channel IDs as channel sessions#80111

Closed
bek91 wants to merge 1 commit into
mainfrom
fix/slack-dm-delivery
Closed

fix:Slack DM delivery mirrors can split sessions by routing bare D... IM channel IDs as channel sessions#80111
bek91 wants to merge 1 commit into
mainfrom
fix/slack-dm-delivery

Conversation

@bek91

@bek91 bek91 commented May 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Slack outbound delivery-mirror session reconstruction for native Slack IM channel IDs (D...). A message.send call can legitimately target Slack’s concrete DM channel, but OpenClaw session routing should treat that same conversation as a direct Slack user session keyed by the peer U... user ID.

This PR updates the Slack outbound route resolver so D... and channel:D... targets resolve through Slack conversation metadata and only create a mirror route when Slack confirms channel.is_im plus channel.user. In that case the mirror session is canonicalized to slack:direct:<u...>, matching inbound Slack DM routing. If the lookup fails or Slack does not return the peer user, the mirror route is skipped instead of writing a known-wrong slack:channel:<d...> session key.

Fixes #80091.

Root Cause

Inbound Slack DM routing already treats D-prefixed Slack channels as IMs and keys the route by the message sender user ID. The outbound delivery-mirror path did not do the same. It parsed bare outbound targets with defaultKind: "channel", only reclassified G... IDs, and therefore allowed a Slack IM channel ID such as D0AEWSDHAQH to produce a session key like:

agent:main:slack:channel:d0aewsdhaqh:thread:1778110574.653649

Later inbound messages in the same visible Slack DM thread route to the canonical direct user session, for example:

agent:main:slack:direct:u09g2dj0275:thread:1778110574.653649

That split loses prior thread context even though the Slack UI shows one DM thread.

What Changed

  • Added Slack conversation metadata lookup that can return both conversation type and the IM peer user.
  • Kept the existing resolveSlackChannelType() contract as a compatibility wrapper.
  • Canonicalized outbound mirror routes for bare D... and channel:D... targets to direct:U... when Slack confirms the mapping.
  • Preserved Slack send delivery behavior. channel:D... can still be used as the concrete Slack delivery target; only mirror/session reconstruction changes.
  • Preserved existing channel and MPIM behavior for C... and G... targets.
  • Avoided caching incomplete native IM metadata so transient Slack lookup failures do not suppress later successful mirror routing.
  • Added regression coverage for the affected paths.

User Impact

After this change, sending into a Slack DM thread through message.send with a native D... target no longer creates a separate channel-scoped OpenClaw session. Outbound delivery mirrors and later inbound DM turns resolve to the same direct user session whenever Slack exposes the peer user mapping.

Existing bad agent:*:slack:channel:d... sessions are not migrated in this PR. This PR prevents new bad mirror sessions from being created.

Real Behavior Proof

Pending live after-fix proof from a real Slack-connected OpenClaw setup.

Planned proof:

  1. Use a real Slack IM channel ID (D...) and real thread timestamp from a Slack DM where OpenClaw is configured.
  2. Confirm live Slack metadata resolves the IM channel to the peer user:
conversations.info(channel=D...)
  channel.is_im: true
  channel.user: U...
  1. Run the same outbound mirror route probe before and after this fix.
  2. Record copied terminal output showing the route changes from the pre-fix channel session:
agent:main:slack:channel:<d...>:thread:<thread_ts>

to the post-fix direct user session:

agent:main:slack:direct:<u...>:thread:<thread_ts>

Unit tests, typechecks, and CI below are supplemental only and do not replace this live proof.

Validation

  • git diff --check
  • pnpm test extensions/slack/src/channel.test.ts extensions/slack/src/channel-type.test.ts
  • pnpm test:extension slack
  • pnpm tsgo:extensions
  • pnpm exec oxfmt --check --threads=1 extensions/slack/src/channel.ts extensions/slack/src/channel-type.ts extensions/slack/src/channel.test.ts extensions/slack/src/channel-type.test.ts CHANGELOG.md
  • codex review --base origin/main

codex review result: no discrete correctness issues found.

AI Assistance

Implemented with Codex. I understand the submitted change and verified the behavior at the code and targeted validation level; live real-behavior proof is pending as noted above.

@openclaw-barnacle openclaw-barnacle Bot added channel: slack Channel integration: slack size: M maintainer Maintainer-authored PR labels May 10, 2026
@bek91 bek91 assigned bek91 and unassigned bek91 May 10, 2026
@bek91
bek91 marked this pull request as ready for review May 10, 2026 04:59
@clawsweeper

clawsweeper Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR adds Slack conversation metadata resolution, canonicalizes outbound delivery-mirror routes for native Slack IM D... targets to direct peer-user sessions, adds regression tests, and updates the changelog.

Reproducibility: yes. source-reproducible. Current main routes bare Slack D... outbound mirror targets as channel peers while docs and inbound Slack routing treat D... IMs as direct user sessions; I did not run a live Slack workspace repro in this read-only review.

Real behavior proof
Needs real behavior proof before merge: Missing: the PR body only lists planned proof and the proof check failed; the contributor should add redacted terminal/log/screenshot/recording proof to the PR body so ClawSweeper can re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Manual review is needed because the protected maintainer label and missing real Slack/OpenClaw proof are merge gates, and there is no narrow code defect for an automated repair lane.

Security
Cleared: The diff only changes Slack routing logic, Slack tests, and changelog text; it does not add dependencies, CI permissions, secrets handling, downloads, or package execution paths.

Review details

Best possible solution:

Land the Slack-owned canonicalization fix after maintainer review and redacted real setup proof, then let it close #80091; any legacy session merge or doctor repair can be handled separately.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible. Current main routes bare Slack D... outbound mirror targets as channel peers while docs and inbound Slack routing treat D... IMs as direct user sessions; I did not run a live Slack workspace repro in this read-only review.

Is this the best way to solve the issue?

Yes, directionally. The narrow maintainable fix belongs in the Slack plugin’s outbound route resolver and conversation-info helper, and this patch preserves channel/MPIM behavior while avoiding new slack:channel:d... mirror keys, but it is not merge-ready without real behavior proof and maintainer handling.

What I checked:

  • Live PR state: The PR is open, non-draft, mergeable, authored by bek91, head a910de2a9d04ac4ea855553e4320aef9b518df2e, and has labels channel: slack, maintainer, and size: M. (a910de2a9d04)
  • Current-main bug path: Current main parses Slack outbound targets with defaultKind: "channel", sets non-user targets to channel by default, and only reclassifies G... IDs through resolveSlackChannelType, so a bare D... target stays a channel peer. (extensions/slack/src/channel.ts:273, 739479254661)
  • Parser contract: Bare Slack IDs fall through to the caller-provided default kind because parseMentionPrefixOrAtUserTarget only parses mentions, prefixes, and @user targets before the default-kind fallback. (extensions/slack/src/target-parsing.ts:48, 739479254661)
  • Expected route behavior: OpenClaw Slack docs state that DMs route as direct, and inbound Slack routing infers D-prefixed channel IDs as IMs and uses the Slack message user for the direct peer. Public docs: docs/channels/slack.md. (docs/channels/slack.md:928, 739479254661)
  • PR route implementation: The PR adds a D... branch in resolveSlackOutboundSessionRoute, resolves conversation metadata, requires type: "dm" plus a peer user, then builds the route from that user rather than the native IM channel ID. (extensions/slack/src/channel.ts:280, a910de2a9d04)
  • PR metadata helper: The PR expands channel-type lookup into resolveSlackConversationInfo, extracts a string channel.user, avoids caching incomplete native IM metadata, and preserves the old resolveSlackChannelType wrapper. (extensions/slack/src/channel-type.ts:17, a910de2a9d04)

Likely related people:

  • steipete: Recent GitHub history shows multiple commits touching extensions/slack/src/channel.ts and Slack session/routing behavior, including outbound delivery target and thread preservation work. (role: recent Slack outbound/session contributor; confidence: high; commits: 05eda57b3c72, 43121fb0968a, e40d7abda9ff; files: extensions/slack/src/channel.ts, extensions/slack/src/channel-type.ts, extensions/slack/src/monitor/message-handler/prepare-routing.ts)
  • bek91: Authored prior merged Slack inbound thread-session routing work that is directly adjacent to the direct-session canonicalization this PR is matching. (role: inbound thread-routing contributor; confidence: medium; commits: aac83e00cfe7; files: extensions/slack/src/monitor/message-handler/prepare-routing.ts)
  • Takhoffman: Recent history for extensions/slack/src/channel-type.ts includes channel-type/default-account behavior, which this PR expands into richer conversation metadata. (role: channel-type helper contributor; confidence: medium; commits: 5f1736266709; files: extensions/slack/src/channel-type.ts)

Remaining risk / open question:

  • No redacted real Slack-connected OpenClaw after-fix proof has been posted yet; the PR body only lists planned proof and the proof check is failing.
  • The fix relies on Slack conversations.info returning channel.user for IMs, which is supported by Slack docs and the issue’s workspace evidence, but still needs live proof on this PR before merge.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 739479254661.

@steipete

Copy link
Copy Markdown
Contributor

Landed a corrected maintainer version in 50c77f2.

What changed from the PR branch:

  • kept the outbound delivery-mirror canonicalization for bare D... and channel:D... Slack IM targets
  • used the Slack SDK-typed path conversations.open({ channel, prevent_creation: true, return_im: true }) for native IM peer-user resolution, because the installed @slack/web-api ConversationsInfoResponse.channel type has is_im/is_mpim but does not type channel.user
  • kept conversations.info for normal channel/MPIM classification
  • skip mirror session creation when a native IM channel cannot be resolved to a peer user, rather than writing a known-wrong slack:channel:d... key
  • added full regression coverage for native D..., channel:D..., unresolved IM, and MPIM behavior

Also landed 4143c8b as a follow-up to #75356 after full Slack tests caught that the new mention parser was too strict for existing loose internal mention tokens.

Validation:

  • pnpm test extensions/slack → 92 files / 973 tests passed
  • pnpm test extensions/slack/src/channel.test.ts extensions/slack/src/channel-type.test.ts
  • pnpm tsgo:extensions
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions/slack/src/channel-type.ts extensions/slack/src/channel-type.test.ts extensions/slack/src/channel.ts extensions/slack/src/channel.test.ts extensions/slack/src/monitor/message-handler/prepare.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/slack/src/channel-type.ts extensions/slack/src/channel-type.test.ts extensions/slack/src/channel.ts extensions/slack/src/channel.test.ts extensions/slack/src/monitor/message-handler/prepare.ts
  • git diff --check HEAD~2..HEAD

check:changed passed conflict-marker, changelog, duplicate, extension typecheck, and extension-test typecheck lanes, then failed in broad lint:extensions on unrelated existing lint findings in Codex, Discord, memory-core, Slack media, Telegram, and Synology test files.

I could not add fresh live Slack proof here because this environment has no Slack creds in env. The code path was checked against the installed Slack SDK types and the linked issue already includes live workspace API evidence for D... -> U... mapping.

Thanks @bek91. The landed commit preserves your contributor credit via Co-authored-by.

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

Labels

channel: slack Channel integration: slack maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack DM delivery mirrors can split sessions by routing bare D... IM channel IDs as channel sessions

2 participants