Skip to content

feat(connector-slack): @peek in a channel — app_mention + threaded auto-continue#155

Merged
harry-harish merged 3 commits into
mainfrom
feat/connector-channel-mention
Jul 9, 2026
Merged

feat(connector-slack): @peek in a channel — app_mention + threaded auto-continue#155
harry-harish merged 3 commits into
mainfrom
feat/connector-channel-mention

Conversation

@harry-harish

@harry-harish harry-harish commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

Makes shared-channel debugging first-class in the peek Slack connector. Today peek answers via the per-user Assistant pane, a DM, and /peek — none of which give the "natural-language + shared-channel + threaded, visible to the whole team" combo that shared debugging needs.

  • app_mention handler@peek what failed in my last session? in a channel → the mention is stripped and routed to the same runtime the assistant/DM use, and peek answers in a thread under the mention (visible to the channel).
  • Threaded auto-continue — once a mention opens a thread, further replies in that thread route to peek without re-mentioning (peek tracks the threads it's active in).
  • app.message firehose fix — the old catch-all answered every message in any channel peek was in. It's now scoped to DMs + peek's active threads.
  • Mention/message dedupe — a channel mention fires both app_mention and message; the message handler skips text containing the bot mention so each is processed exactly once.

The assistant pane, /peek, and the consent/approve/deny paths are unchanged. Acting from a channel thread works via the existing consent card (any channel member may approve in v1; the destructive backstop still runs on the host).

Tests

stripMention (pure helper) + the app_mention and app.message handlers, covering all five message paths (top-level mention, threaded follow-up, unrelated channel message, DM, second in-thread mention) — plus a loud tripwire asserting the Bolt internal listener layout, so a future Bolt bump fails the tests instead of silently mistesting. 58 passing.

Notes

  • No changeset — @peekdev/connector-slack is private: true.
  • Slack app config (maintainer): subscribe to the app_mention bot event + add the app_mentions:read scope, then reinstall.
  • Follow-up (out of scope): optionally restrict action-approval to the connector host.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Slack mentions now open and continue threaded conversations more reliably.
    • Bot mentions are automatically removed from incoming message text before it is processed.
  • Bug Fixes

    • Reduced duplicate message handling when a message includes the bot mention.
    • Improved message filtering so unrelated channel messages are ignored unless they belong to an active thread.
    • Direct messages and thread replies are now handled more consistently, including cases where bot identification is unavailable.

harry-harish and others added 2 commits July 9, 2026 18:54
…to-continue

Adds an app_mention handler (threaded, natural-language answers visible to the
channel), scopes app.message to DMs + peek's active threads (firehose fix), and
dedupes the mention/message double-event. Makes shared-channel debugging
first-class. No runtime/consent change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
…y-botId coverage

Fix 1: getBoltHandler now throws a descriptive Error on out-of-range
index or a missing/empty chain or null callback, converting previously
silent test mis-wiring into a loud failure. A new
"Bolt internal listener layout tripwire" test asserts
boltApp.listeners.length === 5; a Bolt version bump that shifts the
internal layout will fail this assertion rather than silently routing
subsequent handler calls to the wrong index.

Fix 2 (comment only, no logic change): Added a one-line comment on
the idempotent #activeThreads.add(cid) keep-alive inside the
app.message handler in slack-adapter.ts.

Fix 3: New test locks the empty-botUserId branch of the app.message
dedupe guard: when botUserId is absent/empty the botId && ... check
short-circuits and skips the dedupe, so a DM whose text contains a
mention token falls through to the isDM gate and emits normally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harry-harish, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 74c5bea3-e069-4878-a893-8f97e00420dc

📥 Commits

Reviewing files that changed from the base of the PR and between 4600f61 and 8463f57.

📒 Files selected for processing (2)
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
📝 Walkthrough

Walkthrough

Adds an exported stripMention helper to strip bot mention tokens from Slack text, introduces an #activeThreads set on SlackAdapter, adds a new app_mention Bolt listener that activates threads, and updates the message handler to dedupe mentions and gate emission by DM/active-thread status. New tests cover these behaviors.

Changes

Slack mention stripping and thread activation

Layer / File(s) Summary
stripMention utility and active thread state
packages/connector-slack/src/slack-adapter.ts, packages/connector-slack/src/slack-adapter.test.ts
Adds exported stripMention(text, botUserId) that removes bot mention tokens and normalizes whitespace, adds a private #activeThreads set, and adds unit tests validating mention removal, whitespace collapsing, and empty-string edge cases.
app_mention and message handler wiring
packages/connector-slack/src/slack-adapter.ts
Wires an app_mention listener that strips mentions, tracks the thread id in #activeThreads, and emits; updates the message handler to use Bolt context/channel_type, dedupe mention-containing messages against app_mention, and restrict emission to DMs or active threads.
Bolt listener tests for app_mention and message flows
packages/connector-slack/src/slack-adapter.test.ts
Adds a getBoltHandler helper and a listener-count tripwire, plus tests for app_mention emission/thread activation and message handler DM emission, suppression, thread-reply emission, and dedupe (including the no-botUserId bypass case).

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Slack
  participant BoltApp
  participant SlackAdapter

  Slack->>BoltApp: app_mention event
  BoltApp->>SlackAdapter: stripMention(text, botUserId)
  SlackAdapter->>SlackAdapter: activeThreads.add(threadId)
  SlackAdapter->>SlackAdapter: emit(strippedQuery)

  Slack->>BoltApp: message event
  BoltApp->>SlackAdapter: evaluate channel_type / activeThreads / mention dedupe
  alt DM or active thread, not deduped
    SlackAdapter->>SlackAdapter: emit(message)
  else suppressed
    SlackAdapter-->>BoltApp: ignore
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Slack connector change: channel mentions with threaded auto-continue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/connector-channel-mention

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/connector-slack/src/slack-adapter.ts (3)

37-38: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Prefer String.replaceAll over new RegExp for literal token replacement.

botUserId comes from Slack's API (always alphanumeric), so the ReDoS risk is negligible — but replaceAll with a string literal eliminates the regex construction entirely, is simpler, and avoids any future injection surface if the input source ever changes.

♻️ Proposed refactor
 export function stripMention(text: string, botUserId: string): string {
   return text
-    .replace(new RegExp(`<@${botUserId}>`, 'g'), ' ')
+    .replaceAll(`<@${botUserId}>`, ' ')
     .replace(/\s+/g, ' ')
     .trim();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-slack/src/slack-adapter.ts` around lines 37 - 38, The
token replacement in Slack text normalization should avoid building a RegExp for
a literal mention string. Update the replacement logic in the Slack adapter’s
text-processing path to use String.replaceAll with the constructed bot mention
token instead of new RegExp, keeping the behavior of replacing all occurrences
with a space while removing the regex dependency.

63-63: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

#activeThreads grows without bound — consider a cleanup mechanism.

Entries are added on every app_mention (line 215) and thread reply (line 237) but never removed. For the alpha this is fine, but a long-running socket-mode process will accumulate stale thread IDs indefinitely, and threads activated months ago will still trigger the bot on any reply. A TTL-based eviction (e.g. remove entries inactive for N minutes) or a max-size cap would be a low-effort guard for when this moves beyond alpha.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-slack/src/slack-adapter.ts` at line 63, `#activeThreads`
in `SlackAdapter` is only ever added to and never pruned, so the set can grow
indefinitely and keep stale thread IDs active. Add a cleanup mechanism around
the `handleMention`/thread-reply path in `slack-adapter.ts` (where
`#activeThreads` is updated) such as TTL-based expiration or a bounded-size
eviction strategy, and make sure the tracking structure removes inactive entries
automatically over time.

237-237: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Line 237 is a no-op — consider removing.

inActiveThread guarantees this.#activeThreads.has(m.thread_ts) is true and cid === m.thread_ts, so this.#activeThreads.add(cid) always re-adds an existing entry. The comment acknowledges this, but the line adds cognitive overhead for future readers without behavioral value.

♻️ Proposed refactor
-      // Idempotent no-op when cid is already in the set — kept as a defensive keep-alive for threaded replies.
-      if (inActiveThread) this.#activeThreads.add(cid);
       this.emit(cid, m.channel, cid, m.user ?? 'unknown', m.text);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-slack/src/slack-adapter.ts` at line 237, The
`SlackAdapter` thread-handling logic contains a no-op in the `inActiveThread`
branch: `this.#activeThreads.add(cid)` re-adds an already tracked thread ID and
adds no behavior. Remove that statement from the `SlackAdapter` flow around the
`inActiveThread` check, and keep the surrounding thread state logic unchanged so
the intent remains clear in `SlackAdapter` and its `#activeThreads` handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/connector-slack/src/slack-adapter.ts`:
- Around line 229-234: The DM handling in slack-adapter.ts is dropping messages
because the mention dedupe check runs before determining whether the message is
a DM. Update the logic in the Slack message handler so the `<`@botId`>`
suppression only applies when `isDM` is false, and keep the existing
`inActiveThread` and channel-chatter filtering in the same flow within the
relevant message-processing path.

---

Nitpick comments:
In `@packages/connector-slack/src/slack-adapter.ts`:
- Around line 37-38: The token replacement in Slack text normalization should
avoid building a RegExp for a literal mention string. Update the replacement
logic in the Slack adapter’s text-processing path to use String.replaceAll with
the constructed bot mention token instead of new RegExp, keeping the behavior of
replacing all occurrences with a space while removing the regex dependency.
- Line 63: `#activeThreads` in `SlackAdapter` is only ever added to and never
pruned, so the set can grow indefinitely and keep stale thread IDs active. Add a
cleanup mechanism around the `handleMention`/thread-reply path in
`slack-adapter.ts` (where `#activeThreads` is updated) such as TTL-based
expiration or a bounded-size eviction strategy, and make sure the tracking
structure removes inactive entries automatically over time.
- Line 237: The `SlackAdapter` thread-handling logic contains a no-op in the
`inActiveThread` branch: `this.#activeThreads.add(cid)` re-adds an already
tracked thread ID and adds no behavior. Remove that statement from the
`SlackAdapter` flow around the `inActiveThread` check, and keep the surrounding
thread state logic unchanged so the intent remains clear in `SlackAdapter` and
its `#activeThreads` handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 40b1c0ab-258d-4b16-8f73-646ceeecdc31

📥 Commits

Reviewing files that changed from the base of the PR and between e8951fb and 4600f61.

📒 Files selected for processing (2)
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts

Comment thread packages/connector-slack/src/slack-adapter.ts
…laceAll, no-op removal, thread cap

Fix 1 (blocker): compute isDM before the mention-dedupe guard and gate it on
!isDM. A DM whose text contains <@botId> was silently dropped because Slack
never emits app_mention for DMs, leaving nothing to handle it.

Fix 2: stripMention uses String.replaceAll (string literal) instead of
`new RegExp(..., 'g')` — same semantics, no regex construction.

Fix 3: remove the `if (inActiveThread) this.#activeThreads.add(cid)` no-op
in app.message (when inActiveThread is true, cid is already in the set).

Fix 4: bound #activeThreads growth with a 1000-entry insertion-order cap via
a new #trackThread() private helper; app_mention calls #trackThread instead of
the bare .add(). TTL deferred past alpha per CodeRabbit acknowledgement.

Tests: regression for Fix 1 (DM + non-empty botUserId + mention token →
emits), observable-behavior cap test for Fix 4 (oldest evicted, recent alive),
updated empty-botUserId test comment to reflect new guard shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@harry-harish

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@harry-harish

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@harry-harish harry-harish merged commit 844d7b5 into main Jul 9, 2026
7 checks passed
@harry-harish harry-harish deleted the feat/connector-channel-mention branch July 9, 2026 13:52
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