Skip to main content
Auto Threads watches for new messages and, when a message arrives in an eligible channel, immediately creates a dedicated thread for it. The thread title is generated by the AI — if the AI call fails, it falls back to {username}'s thread.

Channel eligibility

A channel is eligible when its channel topic contains the keyword [threads]. You can change the keyword with the AUTOTHREADS_KEYWORD environment variable.
AutoThreads.ts
const isEligibleChannel = (channel: Discord.GetChannel200) =>
  channel?.type === Discord.ChannelTypes.GUILD_TEXT &&
  typeof channel?.topic === "string" &&
  channel.topic.includes(topicKeyword)
Set a channel’s topic to something like Support questions. [threads] to enable auto-threading while keeping a human-readable description.

Message handling

When an eligible message arrives, the bot:
  1. Calls the AI (gpt-5.2) to generate a short title from the message content.
  2. Creates a thread from the message, capped at 100 characters and archived after 24 hours of inactivity.
  3. Posts a control message inside the new thread with Edit title and Archive buttons.
AutoThreads.ts
const handleMessages = gateway.handleDispatch(
  "MESSAGE_CREATE",
  Effect.fnUntraced(
    function* (event) {
      if (!isEligibleMessage(event)) {
        return
      }

      const channel = yield* channels.get(event.guild_id!, event.channel_id)
      if (!isEligibleChannel(channel)) {
        return
      }

      const title = yield* ai.generateTitle(event.content).pipe(
        Effect.tapCause(Effect.log),
        Effect.withSpan("AutoThreads.generateTitle"),
        Effect.orElseSucceed(() => {
          const name = Option.getOrElse(
            Option.fromNullishOr(event.member?.nick),
            () => event.author.username,
          )
          return `${name}'s thread`
        }),
      )

      const thread = yield* rest.createThreadFromMessage(
        channel.id,
        event.id,
        {
          name: Str.truncate(title, 100),
          auto_archive_duration: 1440,
        },
      )

      yield* rest.createMessage(thread.id, {
        components: UI.grid([
          [
            UI.button({
              custom_id: `edit_${event.author.id}`,
              label: "Edit title",
            }),
            UI.button({
              custom_id: `archive_${event.author.id}`,
              label: "Archive",
              style: Discord.ButtonStyleTypes.SECONDARY,
            }),
          ],
        ]),
      })
    },
    // ...
  ),
)

Thread controls

Edit title

Opens a modal with a text input pre-filled with the current thread name. The author of the original message or any member with Manage Channels permission can submit a new title.

Archive

Marks the thread as archived immediately. Same permission check applies.

Permission check

Both buttons check whether the member clicking them is either the original message author or holds the Manage Channels permission. Any other member receives an ephemeral error.
AutoThreads.ts
const canEdit =
  authorId === ix.member?.user?.id || hasManage(ix.member!.permissions!)

Configuration

Environment variableDefaultDescription
AUTOTHREADS_KEYWORD[threads]Keyword the bot looks for in a channel’s topic to enable auto-threading.

Build docs developers (and LLMs) love