Skip to content

feat(slack): add mute/unmute thread command#4

Merged
jld-adriano merged 1 commit intomainfrom
devin/1774999483-mute-command
Mar 31, 2026
Merged

feat(slack): add mute/unmute thread command#4
jld-adriano merged 1 commit intomainfrom
devin/1774999483-mute-command

Conversation

@jld-adriano
Copy link
Copy Markdown

@jld-adriano jld-adriano commented Mar 31, 2026

Summary

Adds mute and unmute commands to the Slack message handler. When a user sends "mute" in a thread, the bot stops responding in that thread unless explicitly @mentioned. Sending "unmute" resumes normal behavior. Both commands are acknowledged with a short reply.

The mute state is stored in-memory per createSlackMessageHandler instance, keyed by channel:threadTs. It is not persisted — a pod restart clears all mute state.

Implementation sits in message-handler.ts alongside the existing aside filter, before the debouncer enqueue step.

Review & Testing Checklist for Human

  • Memory leak risk: mutedThreads is an unbounded Set<string> with no TTL or max size. Unlike the existing seenMessages dedupe cache (which has ttlMs: 60_000, maxSize: 500), muted threads accumulate forever. For low-traffic deployments this is fine; for high-traffic ones, consider whether eviction or a TTL cache is needed.
  • Non-threaded "mute": If someone sends "mute" outside a thread, threadTs falls back to message.ts, which mutes a "thread" of one message. Verify this is acceptable or if non-threaded mute should be rejected.
  • E2E test: In a Slack thread, send "mute" → verify bot acknowledges and stops responding → send a normal message → verify bot ignores it → @mention the bot → verify it responds → send "unmute" → verify bot acknowledges and resumes responding to all messages.

Notes

Link to Devin session: https://app.devin.ai/sessions/460eec68d4c74aaa8c85af43c562f11f
Requested by: @jld-adriano


Open with Devin

When a user sends 'mute' in a thread, the bot stops responding in that
thread unless explicitly @mentioned. Sending 'unmute' resumes normal
behavior. The bot acknowledges both commands with a short reply.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +131 to +132
const threadTs = message.thread_ts ?? message.ts;
const muteKey = threadTs ? `${message.channel}:${threadTs}` : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Mute check runs before thread_ts resolution, causing mute bypass for thread replies with missing thread_ts

The mute key is computed at line 131 using message.thread_ts ?? message.ts, but threadTsResolver.resolve() is called later at line 169. When Slack omits thread_ts on a thread reply (indicated by parent_user_id being set — a known edge case handled by the thread resolver at src/slack/monitor/thread-resolution.ts:81), the mute key will be based on message.ts (the reply's own timestamp) instead of the actual thread root timestamp. This means:

  1. If a thread was correctly muted, subsequent thread replies missing thread_ts will use a different muteKey and bypass the mute check at line 165.
  2. If someone sends "mute" from such a message, the mute will be registered under the wrong key and won't apply to the actual thread.

The debouncer already accounts for this edge case with the maybe-thread key prefix at line 48-49, but the mute logic does not.

Prompt for agents
In src/slack/monitor/message-handler.ts, the mute/unmute key computation and mute-silence check (lines 131-166) should use the resolved thread_ts, not the raw message fields. Move the mute/unmute logic (lines 130-167) to AFTER the threadTsResolver.resolve() call at line 169, and compute threadTs/muteKey from resolvedMessage instead of message. Specifically:

1. Move line 169 (const resolvedMessage = await threadTsResolver.resolve(...)) to just after the aside check (after line 128)
2. Compute threadTs and muteKey from resolvedMessage.thread_ts ?? resolvedMessage.ts instead of message.thread_ts ?? message.ts
3. Use resolvedMessage.channel for the muteKey
4. Pass resolvedMessage through the mute/unmute checks
5. On line 170, enqueue resolvedMessage (already resolved) instead of re-resolving

This ensures the mute key matches the actual thread identity even when Slack omits thread_ts on thread replies.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@jld-adriano jld-adriano merged commit 5e3da7d into main Mar 31, 2026
17 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant