Skip to content

fix: robust Slack DM reply routing to avoid channel_not_found#2284

Closed
imadcat wants to merge 2 commits intoNousResearch:mainfrom
imadcat:fix/slack-dm-routing
Closed

fix: robust Slack DM reply routing to avoid channel_not_found#2284
imadcat wants to merge 2 commits intoNousResearch:mainfrom
imadcat:fix/slack-dm-routing

Conversation

@imadcat
Copy link
Copy Markdown

@imadcat imadcat commented Mar 21, 2026

Summary

Fixes Slack DM replies that fail with channel_not_found by routing via user_id instead of channel_id for IM contexts.

Changes

  • Modified base.py to include channel_type and user_id in metadata for typing and error sends
  • Added DM channel to user_id caching in slack.py
  • Updated send method to prefer user_id for IM channels
  • Adjusted _get_thread_ts to not thread top-level DMs
  • Added comprehensive tests for DM reply threading

Test Plan

  • Unit tests added for top-level and threaded DM replies
  • Verified no channel_not_found errors in DM contexts
- Add metadata for channel_type and user_id to send calls
- Cache DM channel to user_id mapping for fallback
- Prefer user_id over channel_id for IM sends
- Prevent threading in top-level DMs
- Add tests for DM reply threading
Copilot AI review requested due to automatic review settings March 21, 2026 09:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to make Slack DM replies more reliable by avoiding channel_not_found in IM contexts, primarily by routing outbound DM messages via user_id (with a channel→user cache) and by preventing unintended threading for top-level DMs.

Changes:

  • Extend gateway send/typing/error metadata to include channel_type and user_id for platform adapters.
  • Update Slack adapter to cache DM channel→user mappings and prefer user_id for IM/DM sends.
  • Add tests covering DM reply threading behavior (top-level vs threaded DMs).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
gateway/platforms/base.py Adds channel_type and user_id into metadata used for typing and error sends.
gateway/platforms/slack.py Adds DM channel→user cache and routes IM/DM sends via user_id; adjusts DM threading behavior.
tests/gateway/test_slack.py Adds assertions and new test suite for DM reply threading/routing behavior.
proxy.py Introduces a Flask/litellm proxy endpoint (also contains a hard-coded API key).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return metadata["thread_id"]
if metadata.get("thread_ts"):
return metadata["thread_ts"]
if metadata.get("channel_type") == "im":
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

_resolve_thread_ts only treats metadata['channel_type'] == 'im' as a DM, but the gateway sets event.source.chat_type to 'dm' (and base.py forwards that as channel_type). This means top-level DMs will still get thread_ts from reply_to and replies will incorrectly thread. Normalize/lower the channel_type here and treat both 'im' and 'dm' as DM contexts (similar to send()).

Suggested change
if metadata.get("channel_type") == "im":
channel_type = str(metadata.get("channel_type", "")).lower()
if channel_type in ("im", "dm"):
# In DM contexts, only use thread_ts for actual threaded DMs.
# Top-level DMs should reply inline.
Copilot uses AI. Check for mistakes.
Comment on lines +591 to +596
result = await adapter.send(
"D123",
"hello back",
reply_to="1234567890.000001",
metadata={"channel_type": "im", "user_id": "U_USER"},
)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

These DM reply tests use metadata channel_type='im', but in production the gateway passes event.source.chat_type (e.g. 'dm') via base.py metadata. Add coverage for channel_type='dm' (or parametrize) so the test matches the real metadata values and guards against regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +23
import os
import litellm
from flask import Flask, request, jsonify

app = Flask(__name__)

# Configure litellm to use the given key
os.environ["GEMINI_API_KEY"] = "AQ.Ab8RN6JQg5TLwJVPYsu1tlL5L3ccowm8TXWR8c70rljJqw4tFg"

@app.route("/v1/chat/completions", methods=["POST"])
def chat_completions():
try:
data = request.get_json()
model = data.get("model", "gemini/gemini-1.5-pro")
messages = data.get("messages", [])

# Call litellm which handles the translation
response = litellm.completion(
model=model if model.startswith("gemini/") else f"gemini/{model}",
messages=messages,
stream=False # Keep it simple
)
return jsonify(response.model_dump())
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This new Flask/litellm proxy file appears unrelated to the PR’s Slack DM routing purpose and introduces new runtime surface area/dependencies. If it’s not intended for this change, it should be removed from the PR (or moved behind an explicit feature flag / documented entrypoint).

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@imadcat imadcat closed this Mar 21, 2026
@imadcat imadcat deleted the fix/slack-dm-routing branch March 21, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants