Bug
Direct messages to a bot fail with invalid_thread_ts (from chat.postMessage) and invalid_arguments (from chatStream) because handleMessageEvent sets threadTs = "" for top-level DM messages.
Root Cause
In @chat-adapter/slack, handleMessageEvent computes threadTs differently for DMs vs channels:
const threadTs = isDM ? event.thread_ts || "" : event.thread_ts || event.ts;
For channels, top-level messages fall back to event.ts (a valid Slack timestamp).
For DMs, top-level messages fall back to "" (empty string).
This empty string propagates through encodeThreadId → decodeThreadId → Slack API calls:
| Method |
What happens |
Slack error |
postMessage |
thread_ts: "" |
invalid_thread_ts |
chatStream |
thread_ts: "" |
invalid_arguments |
startTyping |
Silently skipped (if (!threadTs) return) |
No error but no typing indicator |
postEphemeral |
Works — already uses threadTs || void 0 |
✅ |
Reproduction
- Create a bot using
chat + @chat-adapter/slack
- Register an
onNewMention handler that calls thread.post("hello")
- Send a DM to the bot (not in a channel)
- Observe:
An API error occurred: invalid_thread_ts
- If using streaming (
thread.post(result.fullStream)): An API error occurred: invalid_arguments
Channel mentions work fine because they fall back to event.ts.
Fix
Use the same fallback for both DMs and channels:
// Before (broken for DMs):
const threadTs = isDM ? event.thread_ts || "" : event.thread_ts || event.ts;
// After (works for both):
const threadTs = event.thread_ts || event.ts;
Additionally, postMessage and chatStream should defensively guard against empty threadTs (like postEphemeral already does):
// postMessage — 3 code paths
thread_ts: threadTs || void 0,
// chatStream
thread_ts: threadTs || void 0,
Workaround
We're using pnpm patch @chat-adapter/slack to apply both fixes. Happy to open a PR if preferred.
Versions
chat: 4.20.2
@chat-adapter/slack: 4.20.2
- Bug also present in 4.18.0
Bug
Direct messages to a bot fail with
invalid_thread_ts(fromchat.postMessage) andinvalid_arguments(fromchatStream) becausehandleMessageEventsetsthreadTs = ""for top-level DM messages.Root Cause
In
@chat-adapter/slack,handleMessageEventcomputesthreadTsdifferently for DMs vs channels:For channels, top-level messages fall back to
event.ts(a valid Slack timestamp).For DMs, top-level messages fall back to
""(empty string).This empty string propagates through
encodeThreadId→decodeThreadId→ Slack API calls:postMessagethread_ts: ""invalid_thread_tschatStreamthread_ts: ""invalid_argumentsstartTypingif (!threadTs) return)postEphemeralthreadTs || void 0Reproduction
chat+@chat-adapter/slackonNewMentionhandler that callsthread.post("hello")An API error occurred: invalid_thread_tsthread.post(result.fullStream)):An API error occurred: invalid_argumentsChannel mentions work fine because they fall back to
event.ts.Fix
Use the same fallback for both DMs and channels:
Additionally,
postMessageandchatStreamshould defensively guard against emptythreadTs(likepostEphemeralalready does):Workaround
We're using
pnpm patch @chat-adapter/slackto apply both fixes. Happy to open a PR if preferred.Versions
chat: 4.20.2@chat-adapter/slack: 4.20.2