Skip to content

feat: AI conversation threading#57

Merged
BillChirico merged 8 commits intomainfrom
feat/ai-conversation-threading
Feb 15, 2026
Merged

feat: AI conversation threading#57
BillChirico merged 8 commits intomainfrom
feat/ai-conversation-threading

Conversation

@BillChirico
Copy link
Collaborator

Summary

When the bot is @mentioned in a regular channel, instead of replying inline (which clutters the channel), it now creates a Discord thread and continues the conversation there. If a thread already exists for that user's recent conversation, it reuses it.

Closes #23

Changes

New: src/modules/threading.js

Thread management module with:

  • Thread creation — creates a thread on the triggering message, titled with a summary of the question
  • Thread reuse — tracks active threads per user+channel, reuses if <30 min old
  • Permission checking — verifies CREATE_PUBLIC_THREADS and SendMessagesInThreads before attempting
  • Graceful fallback — falls back to inline reply when permissions are missing or thread creation fails
  • Archived thread handling — automatically unarchives reused threads

Modified: src/modules/events.js

  • Integrated threading into the message handler flow
  • When threading is enabled and applicable, routes AI responses to threads
  • Conversation history uses thread ID (scoped per-thread) instead of channel ID
  • Falls back to inline message.reply() when threading isn't available

Modified: config.json

New config section under ai.threadMode:

{
  "ai": {
    "threadMode": {
      "enabled": false,
      "autoArchiveMinutes": 60,
      "reuseWindowMinutes": 30
    }
  }
}

Edge Cases Handled

  • ✅ Bot mentioned in existing thread → replies inline (no nested threads)
  • ✅ DMs → no threads (replies normally)
  • ✅ Missing CREATE_PUBLIC_THREADS permission → graceful fallback to inline
  • ✅ Thread deleted/archived between messages → creates new thread
  • ✅ Reuse window expired → creates new thread
  • ✅ Thread creation error → falls back to inline reply

Testing

  • 37 new tests for threading module (100% coverage)
  • 3 new tests for events integration (threading routing, fallback, split messages in threads)
  • All 584 tests pass
  • Coverage: 92.73% statements, 82.78% branches, 85.01% functions, 93.35% lines (all above 80% threshold)

When the bot is @mentioned in a regular channel, create a Discord thread
and continue the conversation there instead of replying inline. If a
thread already exists for that user's recent conversation (within 30
min), reuse it.

- Add threading module with thread creation, reuse, and permission checks
- Integrate threading into message handler with graceful fallback
- Conversation history is scoped to thread ID when in thread mode
- Handle edge cases: existing threads (inline), DMs (no threads),
  missing permissions (fallback to inline)
- Add config options: ai.threadMode.enabled, autoArchiveMinutes,
  reuseWindowMinutes
- 37 new threading tests + 3 new events integration tests
- 100% coverage on threading module

Closes #23
@BillChirico BillChirico self-assigned this Feb 15, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 15, 2026

Warning

Rate limit exceeded

@BillChirico has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between cd2e572 and 4a99c87.

📒 Files selected for processing (4)
  • src/modules/events.js
  • src/modules/threading.js
  • tests/modules/events.test.js
  • tests/modules/threading.test.js
📝 Walkthrough

Walkthrough

Adds configurable thread-based messaging for AI conversations: new threadMode config, a threading module to create/reuse threads, event handler routing to threads when appropriate, and tests for threading behavior and permission flows.

Changes

Cohort / File(s) Summary
Configuration
config.json
Adds ai.threadMode object (enabled, autoArchiveMinutes, reuseWindowMinutes) and minor formatting adjustment.
Threading Module
src/modules/threading.js
New module implementing thread lifecycle: config reading, decision logic (shouldUseThread, canCreateThread), thread keying, name generation, find/create/reuse/unarchive flows, in-memory activeThreads cache, and testing helpers. Review permission checks, caching, and unarchive error handling.
Event Handler Integration
src/modules/events.js
Integrates threading into message handling: determines useThread, obtains targetChannel via getOrCreateThread, sends typing indicators and AI responses to thread or channel, adjusts history ID usage, and preserves existing error/chime behavior. Check response routing differences between inline replies and thread sends.
Tests — Events
tests/modules/events.test.js
Adds tests mocking threading to validate thread routing, fallback to inline replies on creation failure, long-message splitting inside threads, and correct history usage.
Tests — Threading
tests/modules/threading.test.js
Comprehensive unit tests for threading module: config variants, permission checks, name generation, keying, find/create/reuse/unarchive scenarios, error paths, and cache clearing.
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: AI conversation threading' is concise, descriptive, and clearly summarizes the main change (adding AI conversation threading support).
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the threading feature, configuration changes, edge cases, and testing coverage.
Linked Issues check ✅ Passed The PR implementation fully addresses all objectives from issue #23: thread creation, reuse tracking, per-thread history scoping, configuration options, edge case handling, and permission fallbacks are all implemented and tested.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the threading feature scope; config updates, new threading module, events integration, and comprehensive tests are all necessary for the feature.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ai-conversation-threading

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/modules/threading.js`:
- Around line 14-24: The activeThreads Map lacks eviction, so entries can grow
unbounded; update the module to periodically sweep stale entries and/or enforce
a max-size cap: add a cleanup routine (e.g., scheduled with setInterval during
module init) that iterates activeThreads and deletes entries whose lastActive is
older than DEFAULT_REUSE_WINDOW_MS, and optionally implement a simple cap
eviction in the same module (when inserting into activeThreads in functions that
create/update entries, evict the oldest entries if size exceeds a chosen
MAX_ACTIVE_THREADS). Target symbols to change: activeThreads, findExistingThread
(where entries are read/created), and DEFAULT_REUSE_WINDOW_MS (use as the stale
threshold); ensure the cleanup runs off the main request path to avoid blocking.
- Around line 105-124: In generateThreadName, the computed prefix `${username}:
` can push the final name past MAX_THREAD_NAME_LENGTH when username is very
long; update the logic to first ensure the username portion is clamped so prefix
length never exceeds MAX_THREAD_NAME_LENGTH (e.g., truncate username and append
an ellipsis if needed), then compute maxContentLength = MAX_THREAD_NAME_LENGTH -
prefix.length and only include a truncated firstLine up to that non-negative
length; also apply the same length guard to the fallback `Chat with ${username}`
by truncating the username (with ellipsis) so the full fallback never exceeds
MAX_THREAD_NAME_LENGTH. Use the existing symbols generateThreadName,
MAX_THREAD_NAME_LENGTH, prefix, truncatedContent and the fallback string to
locate and adjust the truncation order and clamping.
- Around line 204-207: Validate threadConfig.autoArchiveMinutes before calling
message.startThread: ensure the value is one of Discord's allowed durations (60,
1440, 4320, 10080) and if not either throw/replace it with the nearest allowed
value, then pass that validated value as autoArchiveDuration in the call to
message.startThread (referencing the threadName/thread variable). Update the
logic around message.startThread to use the validatedDuration variable and add a
clear error or snap-to-nearest fallback when threadConfig.autoArchiveMinutes is
invalid.

In `@tests/modules/threading.test.js`:
- Around line 268-296: Add a unit test exercising generateThreadName with a very
long username to ensure name length handling when username alone approaches the
100-char limit; create a new it block that calls
generateThreadName('A'.repeat(98), 'short message') (and optionally another case
with >=100 chars) and assert the result respects the max length (<=100),
includes the username prefix or falls back correctly, and appends truncation
marker if applicable so the behavior is locked down after the threading.js
overflow fix.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 433b679 and 29198d4.

📒 Files selected for processing (5)
  • config.json
  • src/modules/events.js
  • src/modules/threading.js
  • tests/modules/events.test.js
  • tests/modules/threading.test.js
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.test.js

📄 CodeRabbit inference engine (AGENTS.md)

Test coverage must maintain at least 80% threshold on statements, branches, functions, and lines

Files:

  • tests/modules/threading.test.js
  • tests/modules/events.test.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Use ESM modules — import/export syntax; never use require()
Always use node: protocol for Node.js builtins (e.g., import { readFileSync } from 'node:fs')
Always use semicolons in code
Use single quotes for strings (enforced by Biome)
Use 2-space indentation (enforced by Biome)
Always use Winston for logging (import { info, warn, error } from '../logger.js'); never use console.log, console.warn, console.error, or any console.* method
Use custom error classes from src/utils/errors.js and log errors with context before re-throwing
Use getConfig() from src/modules/config.js to read config and setConfigValue(key, value) to update at runtime
Use splitMessage() utility for messages exceeding Discord's 2000-character limit

Files:

  • src/modules/threading.js
  • src/modules/events.js
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/modules/*.js: Register event handlers in src/modules/events.js for all modules before processing in handler functions
Check config.moduleName.enabled before processing in module handler functions

Files:

  • src/modules/threading.js
  • src/modules/events.js
🧠 Learnings (2)
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/modules/*.js : Register event handlers in src/modules/events.js for all modules before processing in handler functions

Applied to files:

  • tests/modules/events.test.js
  • src/modules/events.js
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/**/*.js : Use splitMessage() utility for messages exceeding Discord's 2000-character limit

Applied to files:

  • tests/modules/events.test.js
  • src/modules/events.js
🧬 Code graph analysis (4)
tests/modules/threading.test.js (2)
src/modules/threading.js (18)
  • clearActiveThreads (279-281)
  • config (35-35)
  • getThreadConfig (33-49)
  • shouldUseThread (56-71)
  • canCreateThread (78-96)
  • name (109-109)
  • generateThreadName (105-124)
  • buildThreadKey (132-134)
  • thread (157-157)
  • thread (204-207)
  • thread (257-257)
  • findExistingThread (141-189)
  • key (143-143)
  • key (210-210)
  • getActiveThreads (272-274)
  • createThread (197-225)
  • getOrCreateThread (234-266)
  • existingThread (245-245)
src/modules/config.js (1)
  • getConfig (130-132)
src/modules/threading.js (2)
src/modules/config.js (1)
  • getConfig (130-132)
src/logger.js (2)
  • warn (223-225)
  • info (216-218)
tests/modules/events.test.js (1)
src/modules/threading.js (2)
  • shouldUseThread (56-71)
  • getOrCreateThread (234-266)
src/modules/events.js (1)
src/modules/threading.js (5)
  • shouldUseThread (56-71)
  • getOrCreateThread (234-266)
  • thread (157-157)
  • thread (204-207)
  • thread (257-257)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (9)
src/modules/threading.js (4)

1-12: LGTM — clean module structure, proper imports, and safe config access.

The module follows ESM conventions, uses Winston logging, reads config via getConfig(), and provides sensible defaults with a try/catch fallback. The overall design — separating concerns into small, testable functions — is well done.

Also applies to: 33-49


56-71: LGTM — shouldUseThread, canCreateThread, and buildThreadKey are well-structured.

Good defensive checks: DM guard, thread-inside-thread guard, channel type allowlist, permission checks with proper error handling and fallback to false.

Also applies to: 78-96, 132-134


141-189: LGTM — findExistingThread handles edge cases well.

Expired window, deleted threads, fetch failures, archived thread unarchival with fallback — all properly handled with cleanup of stale map entries.


234-266: LGTM — getOrCreateThread orchestrates the workflow cleanly with proper fallbacks.

Permission check → reuse attempt → create with try/catch → graceful null return on failure. The contract is clear and the caller in events.js can simply check for null.

src/modules/events.js (2)

104-141: LGTM — Threading integration is clean and the fallback logic is correct.

The targetChannel pattern elegantly handles both threaded and inline paths without duplicating the response logic. Reference equality (targetChannel === message.channel) is a reliable way to distinguish the two paths since the variable is directly assigned. History scoping to targetChannel.id correctly uses the thread ID when threaded.


64-67: DM guard at line 67 prevents AI responses in DMs entirely, even without threading.

if (!message.guild) return; rejects all DMs at the top of the handler. This is unrelated to the threading PR but worth noting: if the bot is ever intended to respond to DMs via AI, this guard would need to be scoped more narrowly. Currently shouldUseThread already has its own DM guard, so the early return here is redundant for threading but serves as a general DM block.

config.json (1)

10-14: LGTM — Sensible defaults for the new threadMode configuration.

Thread mode is disabled by default (opt-in), and the default values (60-minute auto-archive, 30-minute reuse window) align with the constants in threading.js.

tests/modules/events.test.js (1)

45-49: LGTM — Good coverage of the three key threading paths in events.

The new tests cover: (1) successful threading with history scoped to thread ID, (2) graceful fallback to inline reply when thread creation fails, and (3) long-message splitting within threads. The mock setup is clean and consistent with the existing test patterns.

Also applies to: 307-390

tests/modules/threading.test.js (1)

1-581: LGTM — Comprehensive test suite with excellent coverage of the threading module.

37 tests covering all exported functions, edge cases (expired/deleted/archived threads, missing permissions, config errors), and state management. The beforeEach/afterEach cleanup pattern prevents test pollution. Well done.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 15, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #58

@cursor
Copy link

cursor bot commented Feb 15, 2026

Bugbot Autofix prepared fixes for 3 of the 3 bugs found in the latest run.

  • ✅ Fixed: Thread cache grows without global eviction
    • Added evictStaleEntries() function called during createThread() that removes expired entries and enforces MAX_CACHE_SIZE=1000 to prevent unbounded memory growth.
  • ✅ Fixed: Concurrent mentions can create duplicate threads
    • Added inFlightCreations Map to track pending thread creation promises so concurrent requests for the same user/channel await the existing promise instead of creating duplicates.
  • ✅ Fixed: Thread mode config accepts invalid durations
    • Added normalizeAutoArchiveDuration() and normalizeReuseWindowMs() functions that validate config values, rejecting NaN/non-positive values and normalizing auto-archive to valid Discord durations (60, 1440, 4320, 10080).

Create PR

Or push these changes by commenting:

@cursor push bb5f42ea64

coderabbitai bot added a commit that referenced this pull request Feb 15, 2026
Docstrings generation was requested by @BillChirico.

* #57 (comment)

The following files were modified:

* `src/modules/events.js`
* `src/modules/threading.js`
coderabbitai bot and others added 5 commits February 15, 2026 18:26
Docstrings generation was requested by @BillChirico.

* #57 (comment)

The following files were modified:

* `src/modules/events.js`
* `src/modules/threading.js`
…cord values

- getThreadConfig now validates reuseWindowMinutes (must be positive number)
  and autoArchiveMinutes (must be valid number), falling back to safe defaults
- Add snapAutoArchiveDuration() that snaps to nearest Discord-allowed value
  (60, 1440, 4320, or 10080 minutes)
- Invalid config values (NaN, negative, non-number) gracefully degrade

Addresses PR #57 review threads #3 and #6.
- Truncate username when it would leave no room for content
- Add final safety clamp to guarantee ≤100 chars in all cases
- Handle edge case where username is ~98+ chars gracefully
- Add tests for very long username scenarios

Addresses PR #57 review threads #5 and #7.
- Add sweepExpiredThreads() that removes entries older than reuse window
- Add MAX_CACHE_SIZE (1000) cap with LRU-style oldest-first eviction
- Start periodic sweep (every 5 min) on module load with unref'd timer
- Export startEvictionTimer/stopEvictionTimer for lifecycle control
- Add tests for sweep logic and size cap enforcement

Addresses PR #57 review threads #1 and #4.
- Add pendingThreadCreations map to serialize concurrent getOrCreateThread
  calls for the same user+channel combination
- Second concurrent call waits for the first to complete, then reuses
  the thread from cache instead of creating a duplicate
- Lock is automatically released after the promise settles
- Add test verifying only one startThread call for concurrent requests

Addresses PR #57 review thread #2.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/events.js (1)

104-141: 🧹 Nitpick | 🔵 Trivial

Threading integration looks solid overall.

The fallback logic (thread → inline), history scoping via targetChannel.id, and response routing are well-structured. The reference-equality check on Line 135 (targetChannel === message.channel) works correctly here since targetChannel is assigned directly from message.channel on Line 106.

One minor observation: when needsSplitting is true and threading is not in use, the response is sent via targetChannel.send(chunk) (Line 133) rather than message.reply(), so the first chunk loses the reply-reference to the original message. This is pre-existing behavior, but threading makes it more visible — you may want to reply to the original message for the first chunk and send the rest when inline.

Optional: preserve reply reference for the first chunk when inline
          if (needsSplitting(response)) {
            const chunks = splitMessage(response);
-           for (const chunk of chunks) {
-             await targetChannel.send(chunk);
-           }
+           for (let i = 0; i < chunks.length; i++) {
+             if (i === 0 && targetChannel === message.channel) {
+               await message.reply(chunks[i]);
+             } else {
+               await targetChannel.send(chunks[i]);
+             }
+           }
🤖 Fix all issues with AI agents
In `@src/modules/events.js`:
- Line 194: There’s a brace-alignment formatting error caused by an unmatched or
misaligned closing brace ('}') at the end of the events module; run the Biome
formatter (biome format) on src/modules/events.js to automatically fix brace
alignment, review the file to ensure the closing brace correctly matches the
surrounding function/module block, and commit the formatted file so CI passes.

In `@src/modules/threading.js`:
- Line 282: CI reports a Biome brace-alignment formatting error at the end of
src/modules/threading.js; run the Biome formatter or adjust the final closing
brace alignment so the file follows project formatting. Specifically, open
src/modules/threading.js and ensure the trailing brace that closes the module
(the final "}" that currently triggers the error) is aligned and followed by a
single newline per Biome rules, then run `biome format` (or your repository's
formatter command) to reformat the file and commit the updated file.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29198d4 and cd2e572.

📒 Files selected for processing (2)
  • src/modules/events.js
  • src/modules/threading.js
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Use ESM modules — import/export syntax; never use require()
Always use node: protocol for Node.js builtins (e.g., import { readFileSync } from 'node:fs')
Always use semicolons in code
Use single quotes for strings (enforced by Biome)
Use 2-space indentation (enforced by Biome)
Always use Winston for logging (import { info, warn, error } from '../logger.js'); never use console.log, console.warn, console.error, or any console.* method
Use custom error classes from src/utils/errors.js and log errors with context before re-throwing
Use getConfig() from src/modules/config.js to read config and setConfigValue(key, value) to update at runtime
Use splitMessage() utility for messages exceeding Discord's 2000-character limit

Files:

  • src/modules/events.js
  • src/modules/threading.js
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/modules/*.js: Register event handlers in src/modules/events.js for all modules before processing in handler functions
Check config.moduleName.enabled before processing in module handler functions

Files:

  • src/modules/events.js
  • src/modules/threading.js
🧠 Learnings (4)
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/modules/*.js : Register event handlers in src/modules/events.js for all modules before processing in handler functions

Applied to files:

  • src/modules/events.js
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/**/*.js : Use splitMessage() utility for messages exceeding Discord's 2000-character limit

Applied to files:

  • src/modules/events.js
  • src/modules/threading.js
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/commands/{timeout,slowmode}.js : Discord timeouts max at 28 days and slowmode caps at 6 hours (21600s); enforce these limits in command logic

Applied to files:

  • src/modules/threading.js
📚 Learning: 2026-02-15T03:14:33.915Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-15T03:14:33.915Z
Learning: Applies to src/commands/{timeout,tempban,slowmode}.js : Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode)

Applied to files:

  • src/modules/threading.js
🧬 Code graph analysis (2)
src/modules/events.js (1)
src/modules/threading.js (5)
  • shouldUseThread (56-71)
  • getOrCreateThread (235-267)
  • thread (159-159)
  • thread (206-209)
  • thread (258-258)
src/modules/threading.js (2)
src/modules/config.js (1)
  • getConfig (130-132)
src/logger.js (2)
  • warn (223-225)
  • info (216-218)
🪛 GitHub Actions: CI
src/modules/events.js

[error] 194-195: Biome formatting error: expected formatting changes (brace alignment) detected by formatting tool.

src/modules/threading.js

[error] 282-283: Biome formatting error: expected formatting changes (brace alignment) detected by formatting tool.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (7)
src/modules/threading.js (7)

1-12: Clean module setup — ESM imports, proper logging, good module doc.


33-49: Defensive getThreadConfig with safe defaults — LGTM.


56-71: shouldUseThread logic is correct and covers all edge cases.

DMs, existing threads, and non-threadable channel types are all properly excluded.


78-96: Permission checks are well-handled with a safe false fallback on error.


199-227: message.author.displayName may be undefined in some contexts.

displayName is a property on GuildMember, not User. On the User object (message.author), displayName was added in discord.js v14.11 as an alias for globalName ?? username. This should work with v14.25.1, but the fallback chain message.author.displayName || message.author.username is correct either way.


235-267: getOrCreateThread — clean orchestration with proper fallback.

Permission check → reuse → create → fallback is well-structured with appropriate logging at each step.


156-190: Remove the null-check claim — threads.fetch() in discord.js v14 can legitimately return null.

In discord.js v14, ThreadManager.fetch(id) returns Promise<ThreadChannel | null>, where null is returned when a thread exists but doesn't belong to the current channel's thread manager. It only throws (DiscordAPIError) when the thread doesn't exist or is inaccessible. The if (!thread) check on line 160 is not dead code—it's a valid guard for the wrong-parent-channel case.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

When threading is enabled and ai.channels is configured, follow-up
messages inside threads were being dropped because isAllowedChannel
checked message.channel.id (the thread ID) instead of the parent
channel ID. Now uses isThread() to detect threads and checks
parentId against the allowlist.

Adds tests for thread allowlist behavior (allow when parent matches,
block when parent doesn't match).
@BillChirico BillChirico merged commit 3f6aca7 into main Feb 15, 2026
4 checks passed
BillChirico pushed a commit that referenced this pull request Feb 15, 2026
Docstrings generation was requested by @BillChirico.

* #57 (comment)

The following files were modified:

* `src/modules/events.js`
* `src/modules/threading.js`
BillChirico added a commit that referenced this pull request Feb 15, 2026
…cord values

- getThreadConfig now validates reuseWindowMinutes (must be positive number)
  and autoArchiveMinutes (must be valid number), falling back to safe defaults
- Add snapAutoArchiveDuration() that snaps to nearest Discord-allowed value
  (60, 1440, 4320, or 10080 minutes)
- Invalid config values (NaN, negative, non-number) gracefully degrade

Addresses PR #57 review threads #3 and #6.
@BillChirico BillChirico deleted the feat/ai-conversation-threading branch February 15, 2026 23:45
BillChirico added a commit that referenced this pull request Feb 15, 2026
- Truncate username when it would leave no room for content
- Add final safety clamp to guarantee ≤100 chars in all cases
- Handle edge case where username is ~98+ chars gracefully
- Add tests for very long username scenarios

Addresses PR #57 review threads #5 and #7.
BillChirico added a commit that referenced this pull request Feb 15, 2026
- Add sweepExpiredThreads() that removes entries older than reuse window
- Add MAX_CACHE_SIZE (1000) cap with LRU-style oldest-first eviction
- Start periodic sweep (every 5 min) on module load with unref'd timer
- Export startEvictionTimer/stopEvictionTimer for lifecycle control
- Add tests for sweep logic and size cap enforcement

Addresses PR #57 review threads #1 and #4.
BillChirico added a commit that referenced this pull request Feb 15, 2026
- Add pendingThreadCreations map to serialize concurrent getOrCreateThread
  calls for the same user+channel combination
- Second concurrent call waits for the first to complete, then reuses
  the thread from cache instead of creating a duplicate
- Lock is automatically released after the promise settles
- Add test verifying only one startThread call for concurrent requests

Addresses PR #57 review thread #2.
@coderabbitai coderabbitai bot mentioned this pull request Mar 3, 2026
12 tasks
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.

AI conversation threading — auto-create Discord threads for multi-turn AI chats

1 participant