deno-slack-sdk icon indicating copy to clipboard operation
deno-slack-sdk copied to clipboard

[QUERY] How to unify thread_ts vs message_ts from a `MessagePosted` event?

Open NickCrews opened this issue 4 months ago • 5 comments

Question

What I want to do is get any images from the exact message that my bot was tagged in. I have this trigger:

import type { Trigger } from "deno-slack-sdk/types.ts";
import { TriggerContextData, TriggerTypes, TriggerEventTypes } from "deno-slack-api/mod.ts";
import { TagCapnWorkflow } from "../workflows/RespondToTag.ts";

const trigger: Trigger = {
  type: TriggerTypes.Event,
  name: "Ask Capn",
  description: "use @capn to tag Capn in a channel message",
  workflow: `#/workflows/${TagCapnWorkflow.id}`,
  event: {
    event_type: TriggerEventTypes.AppMentioned,
    all_resources: true,
  },
  inputs: {
    user_id: {
      value: TriggerContextData.Event.MessagePosted.user_id,
    },
    text: {
      value: TriggerContextData.Event.MessagePosted.text,
    },
    channel_id: {
      value: TriggerContextData.Event.MessagePosted.channel_id,
    },
    message_ts: {
      // In the case this is a threaded message, this property becomes the message_ts of the parent message.
      // Thus, if we are were tagged in a thread, this is the ts of the original message, not where we were tagged.
      // TODO: should also pass in TriggerContextData.Event.MessagePosted.thread_ts,
      // but this is sometimes null and therefore causes schema validation errors???
      value: TriggerContextData.Event.MessagePosted.message_ts,
    },
    thread_ts: {
      value: TriggerContextData.Event.MessagePosted.thread_ts,
    },
  },
};

export default trigger;

From reading the docs (well, intellisense on TriggerContextData.Event.MessagePosted.thread_ts) l, my expectation was

  • if the bot is tagged in the a thread, then
    • message_ts is the top-level message
    • thread_ts is the specific message that the bot was tagged in. This is the mesage I want to look for files in.
  • if the bot is tagged in top-level of the channel, then
    • message_ts is a value (and is the message I want to look for files in)
    • thread_ts is NULL.

OK, so then I have this workflow:

import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { RespondToCapnTagFunction } from "../functions/respond_to_tag_capn/definition.ts";

export const TagCapnWorkflow = DefineWorkflow({
  callback_id: "tag_capn",
  title: "Tag Capn",
  description:
    "use @capn to tag Capn in a channel message",
  input_parameters: {
    properties: {
      user_id: {
        type: Schema.slack.types.user_id,
        description: "The ID of the user who tagged Capn",
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
        description: "The ID of the channel where Capn was tagged",
      },
      text: {
        type: Schema.types.string,
        description: "The text of the message where Capn was tagged",
      },
      message_ts: {
        type: Schema.slack.types.message_ts,
        description: "The timestamp of the message where Capn was tagged",
      },
      thread_ts: {
        type: Schema.slack.types.message_ts,
        description: "The timestamp of the thread where Capn was tagged",
      }
    },
    required: ["user_id", "channel_id", "text", "message_ts"],
  },
});

TagCapnWorkflow.addStep(RespondToCapnTagFunction, {
  text: TagCapnWorkflow.inputs.text,
  user_id: TagCapnWorkflow.inputs.user_id,
  channel_id: TagCapnWorkflow.inputs.channel_id,
  message_ts: TagCapnWorkflow.inputs.message_ts,
  thread_ts: TagCapnWorkflow.inputs.thread_ts,
});

But, when I tag the bot in either the top level of a channel, OR in a thread, I get this error:

2025-10-23 14:03:14 [error] [Wf09MXNHK1K5] (Trace=Tr09NDTDF28L) Trigger for workflow 'Tag Capn' failed: parameter_validation_failed
2025-10-23 14:03:14 [error] [Wf09MXNHK1K5] (Trace=Tr09NDTDF28L)   - Null value for non-nullable parameter `thread_ts`

Can you help me understand this? Can you give me a solution to my goal?

Environment

Paste the output of cat import_map.json | grep deno-slack

NA

Paste the output of deno --version

deno 2.3.3 (stable, release, aarch64-apple-darwin) v8 13.7.152.6-rusty typescript 5.8.3

Paste the output of sw_vers && uname -v on macOS/Linux or ver on Windows OS

ProductName: macOS ProductVersion: 15.6.1 BuildVersion: 24G90 Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:29 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6000

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

NickCrews avatar Oct 23 '25 20:10 NickCrews

Oh, am I being silly, is it because I am triggering on AppMentioned, but I am trying to reference MessagePosted data fields?

I just found https://docs.slack.dev/tools/deno-slack-sdk/guides/creating-event-triggers#response-object

It looks like the AppMentioned event contains:

{
  "team_id": "T0123ABC",
  "enterprise_id": "E0123ABC",
  "event_id": "Ev0123ABC",
  "event_timestamp": 1643810217.088700,
  "type": "event",
  "data": {
    "app_id": "A1234ABC",
    "channel_id": "C0123ABC",
    "channel_name": "cool-channel",
    "channel_type": "public/private/dm/mpdm",
    "event_type": "slack#/events/app_mentioned",
    "message_ts": "164432432542.2353",
    "text": "<@U0LAN0Z89> is it everything a river should be?",
    "user_id:": "U0123ABC",
  }
}

and MessagePosted contains

{
  "team_id": "T0123ABC",
  "enterprise_id": "E0123ABC",
  "event_id": "Ev0123ABC",
  "event_timestamp": 1630623713,
  "type": "event",
  "data": {
    "channel_id": "C0123ABC",
    "channel_type": "public/private/dm/mpdm",
    "event_type": "slack#/events/message_posted",
    "message_ts": "1355517523.000005",
    "text": "Hello world",
    "thread_ts": "1355517523.000006", // Nullable
    "user_id": "U0123ABC",
  }
}

so they are ALMOST the same, except AppMentioned doesn't have thread_ts. Is this an oversight in the API? Can you adjust the API so that the AppMentioned ALSO has the thread_ts property?

NickCrews avatar Oct 23 '25 20:10 NickCrews

I'm pretty sure this is wrong:

if the bot is tagged in the a thread, then message_ts is the top-level message thread_ts is the specific message that the bot was tagged in. This is the mesage I want to look for files in.

  • message_ts is always the timestamp of the message.
  • thread_ts is the timestamp of the thread (if the message is in a thread, NULL otherwise).

So you should be able to deal with messages without having to worry about whether or not they're in a thread (unless you want to reply in that same thread).

~If you really need the thread_ts you could call the conversation.history API endpoint in order to retrieve a single message (based on the message_ts), which will return the thread_ts (if there is one). See documentation here.~

~Ah, nvm.... we need to call conversations.replies in order to retrieve messages from a thread, which requires the thread_id ahead of time. 🤦‍♂️~

~According to this, the only way to spot if a message is in a thread is via the thread_ts parameter. If that parameter is not provided, then we're out of luck. 😩~

Detect a threaded message by looking for a thread_ts value in the message object. The existence of such a value indicates that the message is part of a thread.

Ok, actually, conversations.replies is the way to go!

It's very simple, using the ts of any message: https://slack.com/api/conversations.replies?channel=C1C1C1C1C1&ts=1234567890.123456

  • If the message is a regular message without thread/replies, the response will only contain ts and no thread_ts.
  • If the message is a regular message with a thread/replies, the response will contain ts as well as thread_ts, and both values will be the same (along with other things like reply_count, reply_users_count, and latest_reply).
  • If the message is a reply within a thread, the response will contain ts as well as thread_ts, and the values will be different (and there won't be any other things like reply_count, reply_users_count, and latest_reply).

I posted the answer on StackOverflow (here), because I remember struggling with this in the past.

mroy-seedbox avatar Oct 23 '25 20:10 mroy-seedbox

  • message_ts is always the timestamp of the message.
  • thread_ts is the timestamp of the thread (if the message is in a thread, NULL otherwise).

You are right, and that suggestion of conversations.replies works great! Thank you!

Going forward, some free ™️ docs suggestions from me:

What do you think about adding a big fat warning to https://docs.slack.dev/reference/methods/conversations.history that this does NOT include messages that are replies? I was using this because I found this section, that looked like a bullseye to me. But when I tried it, I kept getting 0 messages back when I queried a message that was a reply.

And maybe augmenting with conversations.replies docs with something along the lines of "use this instead of conversations.replies when..."

Also, this doc keeps talking about "Individual messages" which I think NOW are adding the "individual" to contrast against "threaded", but that is super not clear from just that article. I thought it was contrasting vs "multiple". Perhaps rename to "standalone" instead of "individual"? or otherwise be more clear here.

Also this doc correctly links to https://docs.slack.dev/messaging/retrieving-messages#finding_threads, (note the #finding_threads) anchor, BUT when I actually open that URL, the docs site (on a recent chrome) misleadingly scrolls to center the section on "individual messages". Note how the table of contents is corrently highlighted, but the viewport hasn't scrolled down enough.

Image

This made me think I was supposed to read this section, which was a total red herring. So tell your docs team to fix this #anchor-section issue

NickCrews avatar Oct 23 '25 21:10 NickCrews

I 💯% agree with your doc suggestions.

This has always been so confusing to me, and the doc has never been helpful at all. I had to figure it out via trial & error.

We shouldn't need a StackOverflow answer to sort out the confusion caused by the documentation. Even LLMs are confused by this and are hallucinating wrong answers.

conversations.replies is a much better way to retrieve individual messages (whether or not they are in a thread).

This is a pretty foundational feature of a messaging system, so you'd think the documentation would be solid, but it really needs some love/work.... 😖

I hope the Slack team sees this. 🤞


What do you think about adding a big fat warning to https://docs.slack.dev/reference/methods/conversations.history that this does NOT include messages that are replies? I was using this because I found this section, that looked like a bullseye to me. But when I tried it, I kept getting 0 messages back when I queried a message that was a reply.

YES! That is the most confusing part. And it's even worse: you will only get 0 messages back if the message/reply is the most recent in the entire channel. Otherwise, you might get an entirely different message (i.e. the most recent channel message after that timestamp).


Also, this doc keeps talking about "Individual messages" which I think NOW are adding the "individual" to contrast against "threaded", but that is super not clear from just that article. I thought it was contrasting vs "multiple". Perhaps rename to "standalone" instead of "individual"? or otherwise be more clear here.

The API example is also clearly broken, mentions "additional parameters" which are not in the example, and is using conversations.history, which will not work for messages within a thread:

You can read the conversations.history reference for a more in-depth explanation of what these additional parameters do, but essentially we're telling the API to return one result from the conversation's history, using the message ts as a starting point.

Image

Also this doc correctly links to https://docs.slack.dev/messaging/retrieving-messages#finding_threads, (note the #finding_threads) anchor, BUT when I actually open that URL, the docs site (on a recent chrome) misleadingly scrolls to center the section on "individual messages". Note how the table of contents is correctly highlighted, but the viewport hasn't scrolled down enough.

More problems:

  • The example provided is only valid is the message is the parent message of a thread.
    • For messages without replies, it will not look like that at all.
    • For replies within a thread, it will not look like that at all either.
  • It is talking about receiving or retrieving messages without much additional guidance on how to do so (especially the retrieval part).

mroy-seedbox avatar Oct 23 '25 22:10 mroy-seedbox

@NickCrews: Thank you for posting this issue by the way. You gave me the motivation to finally do something about all my internal frustration about this and finally figure it out for good. 🙏💙

It's hard to swim against the current when the documentation tells you to go one way, but we actually have to just ignore it and go in a different direction. 😖

mroy-seedbox avatar Oct 23 '25 22:10 mroy-seedbox