Skip to content

fix(slack): classify D-prefix DMs correctly when channel_type disagrees#10630

Closed
mcaxtr wants to merge 6 commits intoopenclaw:mainfrom
mcaxtr:fix/8421-slack-dm-classification
Closed

fix(slack): classify D-prefix DMs correctly when channel_type disagrees#10630
mcaxtr wants to merge 6 commits intoopenclaw:mainfrom
mcaxtr:fix/8421-slack-dm-classification

Conversation

@mcaxtr
Copy link
Contributor

@mcaxtr mcaxtr commented Feb 6, 2026

Summary

Fixes #8421

Slack DMs (channel IDs starting with D) are misclassified as channels/groups when the event payload provides a channel_type that disagrees with the channel ID prefix. This causes DMs to bypass dmScope: "main" routing and create separate per-channel sessions instead of merging with the main session.

lobster-biscuit

Repro Steps

  1. Configure session.dmScope: "main" with channels.slack.dm.policy: "open"
  2. Send a Slack DM to the bot
  3. Observe the DM creates a session key like agent:main:slack:channel:d0acp6b1t8v instead of agent:main:main
  4. Outbound replies don't auto-deliver because they're routed to the wrong session

Root Cause

normalizeSlackChannelType() in context.ts trusts the event's channel_type field even when it contradicts the channel ID prefix. Slack DM channel IDs always start with D, which definitively indicates a DM. When Slack sends an incorrect channel_type (e.g., "channel" or "group" for a D-prefix ID), the normalization function returns the wrong type without cross-validating against the ID prefix. This causes downstream routing to use peer.kind = "channel" instead of "dm", completely bypassing dmScope session logic.

Behavior Changes

  • normalizeSlackChannelType() now cross-validates: when a channel ID starts with D (inferred type "im"), any contradicting channel_type is overridden to "im"
  • No change for C-prefix or G-prefix channels (G-prefix is ambiguous between private channel and group DM, so the provided type is trusted)
  • All existing callers benefit: prepareSlackMessage, isChannelAllowed, resolveSlackSystemEventSessionKey

Codebase and GitHub Search

  • Searched PRs by issue number and topic keywords (slack DM misclassified, slack dm kind, slack dmScope): 0 competing PRs
  • Searched for duplicate issues: only Slack DMs Misclassified as Channels - dmScope 'main' Not Working #8421 describes this specific classification bug
  • Reviewed all callers of normalizeSlackChannelType to confirm the fix covers all code paths

Tests

  • pnpm build — passes
  • pnpm check — passes (0 warnings, 0 errors)
  • pnpm test — full suite passes

New tests (6 total, all fail before fix, pass after):

context.test.ts (3 new):

  • overrides wrong channel_type for D-prefix DM channels — verifies "channel", "group", "mpim" all resolve to "im" for D-prefix IDs
  • preserves correct channel_type for D-prefix DM channels — sanity check that "im" stays "im"
  • does not override G-prefix channel_type — verifies ambiguous G-prefix preserves provided type

prepare.inbound-contract.test.ts (2 new):

  • classifies D-prefix DMs correctly even when channel_type is wrong — integration test: D-prefix + channel_type: "channel"isDirectMessage: true, session routes to agent:main:main, ChatType: "direct"
  • classifies D-prefix DMs when channel_type is missing — integration test: D-prefix + no channel_type → correctly inferred as DM

Evidence

Before fix:

normalizeSlackChannelType("channel", "D123") → "channel"  // WRONG
// Session key: agent:main:slack:channel:d123 (bypasses dmScope)

After fix:

normalizeSlackChannelType("channel", "D123") → "im"  // CORRECT
// Session key: agent:main:main (respects dmScope: "main")

Greptile Overview

Greptile Summary

  • Fixes Slack DM misclassification by cross-validating channel_type against the channel ID prefix (D-prefix always treated as im).
  • Adds unit + integration coverage to ensure D-prefix DMs route through dmScope: "main" even with missing/incorrect channel_type.
  • Updates Discord reaction handling to no longer drop DM reactions and to route them using peer.kind/peer.id based on channel type.
  • Performs broad docs/markdownlint formatting changes across many docs files (link formatting + lint rule adjustments).

Confidence Score: 4/5

  • This PR is mostly safe to merge, with one test typing issue to fix before merge.
  • Core Slack fix is minimal and well-targeted with added coverage. The only clear blocker is an invalid TypeScript cast in the new Discord DM reaction tests that can break typechecking/builds depending on TS settings.
  • src/discord/monitor.test.ts

mcaxtr and others added 6 commits February 6, 2026 11:51
…w#10476)

* docs(markdownlint): enable autofixable rules except list numbering

* docs(zalo): fix malformed bot platform link
…aw#10499)

* docs(install): revamp installer internals for readability and accuracy

Restructure the installer internals page with better flow and Mintlify
components (CardGroup, Steps, Tabs, AccordionGroup). All flags, env vars,
and behavioral descriptions cross-checked against install.sh,
install-cli.sh, and install.ps1 source code.

- Add CardGroup chooser and Quick Commands section at top
- Organize each script into consistent Flow → Examples → Reference pattern
- Move flags/env var tables into collapsible Accordions
- Consolidate troubleshooting into AccordionGroup at bottom
- Add missing flags (--version, --beta, --verbose, --help, etc.)
- Add missing env vars (OPENCLAW_VERSION, OPENCLAW_BETA, etc.)
- Document install-cli.sh fully (was one paragraph)
- Fix non-interactive checkout detection behavior (defaults to npm)
- Use --proto/--tlsv1.2 in curl examples to match script usage
- No content deleted; all original info preserved or relocated

* fix(docs): correct in-page anchor hrefs for installer cards

* docs(install): replace CardGroup with table for installer overview
@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: line Channel integration: line channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo app: android App: android app: macos App: macos gateway Gateway runtime docker Docker and sandbox tooling channel: feishu Channel integration: feishu labels Feb 6, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +785 to +792
username: overrides?.botAsAuthor ? "bot" : "otheruser",
discriminator: "0",
},
})),
},
} as unknown as Parameters<DiscordReactionListener["handle"]>[0];
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid cfg type cast

makeReactionListenerParams() sets cfg to {} as ReturnType<... ? never : never>, which resolves to never and makes cfg unusable/incorrectly typed at compile time. This should be a concrete LoadedConfig/config type expected by DiscordReactionListener (or a minimal stub of that type), otherwise TS builds can fail or the test will rely on as unknown as casts elsewhere.

Suggested change
username: overrides?.botAsAuthor ? "bot" : "otheruser",
discriminator: "0",
},
})),
},
} as unknown as Parameters<DiscordReactionListener["handle"]>[0];
}
cfg: {} as import("../config/load.js").LoadedConfig,
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor.test.ts
Line: 785:792

Comment:
**Invalid cfg type cast**

`makeReactionListenerParams()` sets `cfg` to `{} as ReturnType<... ? never : never>`, which resolves to `never` and makes `cfg` unusable/incorrectly typed at compile time. This should be a concrete `LoadedConfig`/config type expected by `DiscordReactionListener` (or a minimal stub of that type), otherwise TS builds can fail or the test will rely on `as unknown as` casts elsewhere.

```suggestion
    cfg: {} as import("../config/load.js").LoadedConfig,
```

How can I resolve this? If you propose a fix, please make it concise.

@mcaxtr
Copy link
Contributor Author

mcaxtr commented Feb 6, 2026

Closing in favor of a clean PR with only the relevant changes (the original included unrelated commits from fork sync).

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

Labels

app: android App: android app: macos App: macos channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: line Channel integration: line channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo docker Docker and sandbox tooling docs Improvements or additions to documentation gateway Gateway runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack DMs Misclassified as Channels - dmScope 'main' Not Working

2 participants