fix: address code review findings from PRs #12–#18#19
fix: address code review findings from PRs #12–#18#19BillChirico merged 3 commits intoVolvoxLLC:mainfrom
Conversation
Resolve six issues identified during review of the last five merged PRs: - Wrap Discord message sends in try-catch with user-friendly fallback - Invert NODE_ENV check to hide error details by default - Remove duplicate unhandledRejection handler from index.js - Remove dead graceful-shutdown request tracking code - Null out ChimeIn AbortController in finally block - Add throttled eviction to welcome activity Map
📝 WalkthroughSummary by CodeRabbit
WalkthroughSanitized config error messages by environment, removed pending-request helpers and unhandledRejection shutdown handling, cleared AbortController references after evaluation, added robust AI-response send error handling with user-friendly replies, and implemented throttled pruning of stale community activity; tests added or updated accordingly. Changes
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@tests/commands/config.test.js`:
- Around line 173-195: The test restores NODE_ENV incorrectly (setting it to the
string "undefined") — update the teardown to restore env safely by checking
originalEnv: if originalEnv === undefined then delete process.env.NODE_ENV else
set process.env.NODE_ENV = originalEnv; additionally move the test logic into a
try/finally around the call to execute (and the mocks) so the environment is
always restored; reference the test block that calls execute, uses getConfig
mock, and uses originalEnv.
In `@tests/modules/welcome.test.js`:
- Around line 139-174: The test currently only ensures no crash when eviction
runs but doesn't assert pruning occurred; update the test around
recordCommunityActivity and the stale channel to verify pruning by using or
adding a public inspector (e.g., getCommunitySnapshot or similar) to read the
stored timestamps for guild 'prune-guild' and channels 'prune-ch' and 'fresh-ch'
after the simulated time jump and repeated calls; specifically, after the
vi.restoreAllMocks() call invoke getCommunitySnapshot() (or add/export such a
helper from the welcome module) and assert that 'prune-ch' has only fresh
timestamps (i.e., no timestamps older than the activityWindowMinutes) or that a
subsequent call to recordCommunityActivity for 'prune-ch' creates a new entry
rather than reusing old timestamps, ensuring deterministic behavior regardless
of module-level activityCallCount.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
src/commands/config.jssrc/index.jssrc/modules/chimeIn.jssrc/modules/events.jssrc/modules/welcome.jstests/commands/config.test.jstests/index.test.jstests/modules/events.test.jstests/modules/welcome.test.js
💤 Files with no reviewable changes (1)
- tests/index.test.js
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.js: Use ESM syntax withimport/export; never userequire()
Always usenode:protocol when importing 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 logging viaimport { info, warn, error } from '../logger.js'— NEVER useconsole.log(),console.warn(),console.error(), or anyconsole.*method in src/ files
Pass structured metadata when logging:info('Message processed', { userId, channelId })
Use custom error classes fromsrc/utils/errors.jsfor error handling
Always log errors with context before re-throwing
UsegetConfig()fromsrc/modules/config.jsto read config; usesetConfigValue(key, value)to update at runtime
UsesplitMessage()utility for messages exceeding Discord's 2000-character limit
No TypeScript — use plain JavaScript with JSDoc comments for documentation
Files:
src/modules/chimeIn.jssrc/index.jssrc/modules/welcome.jssrc/modules/events.jssrc/commands/config.js
src/modules/*.js
📄 CodeRabbit inference engine (AGENTS.md)
Check
config.yourModule.enabledbefore processing in module handlers
Files:
src/modules/chimeIn.jssrc/modules/welcome.jssrc/modules/events.js
tests/**/*.{js,test.js}
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest for testing with
pnpm test; test coverage provider is@vitest/coverage-v8
Files:
tests/modules/welcome.test.jstests/modules/events.test.jstests/commands/config.test.js
src/commands/*.js
📄 CodeRabbit inference engine (AGENTS.md)
Slash commands must export
data(SlashCommandBuilder) andexecute(interaction)function
Files:
src/commands/config.js
🧠 Learnings (4)
📚 Learning: 2026-02-11T17:18:29.603Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-11T17:18:29.603Z
Learning: Graceful shutdown is handled in `src/index.js`; use error classes and logging before throwing errors in other modules
Applied to files:
src/index.js
📚 Learning: 2026-02-11T17:18:29.603Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-11T17:18:29.603Z
Learning: Applies to src/**/*.js : Use `splitMessage()` utility for messages exceeding Discord's 2000-character limit
Applied to files:
src/modules/events.jstests/modules/events.test.js
📚 Learning: 2026-02-11T17:18:29.603Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-11T17:18:29.603Z
Learning: Module handlers must be registered in `src/modules/events.js` to wire them to Discord events
Applied to files:
tests/modules/events.test.js
📚 Learning: 2026-02-11T17:18:29.603Z
Learnt from: CR
Repo: BillChirico/bills-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-11T17:18:29.603Z
Learning: Applies to src/**/*.js : Use custom error classes from `src/utils/errors.js` for error handling
Applied to files:
tests/modules/events.test.js
🧬 Code graph analysis (5)
src/index.js (3)
src/modules/ai.js (1)
stopConversationCleanup(335-341)src/logger.js (2)
info(216-218)error(230-232)src/db.js (1)
closeDb(149-160)
src/modules/events.js (3)
src/utils/errors.js (2)
message(46-46)getUserFriendlyMessage(114-159)src/modules/ai.js (4)
response(406-417)generateResponse(379-450)config(34-34)config(49-49)src/utils/splitMessage.js (3)
needsSplitting(59-61)chunks(29-29)splitMessage(24-51)
src/commands/config.js (1)
src/modules/config.js (1)
err(30-30)
tests/modules/events.test.js (3)
src/modules/welcome.js (1)
message(117-123)src/utils/errors.js (2)
message(46-46)getUserFriendlyMessage(114-159)src/utils/splitMessage.js (2)
needsSplitting(59-61)splitMessage(24-51)
tests/commands/config.test.js (2)
src/modules/config.js (1)
getConfig(130-132)src/commands/config.js (3)
execute(158-178)content(315-315)content(350-350)
🔇 Additional comments (7)
src/modules/chimeIn.js (1)
283-286: LGTM — proper cleanup ofabortControllerin thefinallyblock.This ensures the
AbortControllerreference doesn't linger after evaluation completes normally, complementing the abort-and-null inresetCounter.src/commands/config.js (1)
258-265: LGTM — consistent environment-aware error messaging across all three handlers.The inverted check correctly restricts error detail exposure to explicit
developmentmode only, preventing accidental leakage of internal details (e.g., database connection strings) in production or unset environments.src/modules/welcome.js (1)
75-100: LGTM — throttled eviction prevents unbounded Map growth.The approach of pruning every 50 calls with the current activity window cutoff is clean and avoids per-call overhead. Deleting from a
Mapduringfor...ofiteration is safe in JavaScript.src/modules/events.js (1)
97-133: Solid error handling for Discord send failures.The try/catch correctly wraps all Discord API calls in the AI response path, and the nested fallback with silent catch on line 130 is appropriate — if the initial send failed due to permissions or a deleted channel, the fallback will also fail gracefully.
src/index.js (1)
203-228: Clean simplification of the shutdown sequence.The removal of the pending-request wait loop streamlines shutdown without losing essential steps (stop timers → save state → close DB → disconnect → exit). The ordering is correct.
tests/modules/events.test.js (2)
247-281: Good coverage of the new error paths.Both tests correctly exercise the failure scenarios — reply rejection triggering the
getUserFriendlyMessagefallback, and split-send rejection where bothsendand the fallbackreplyfail. The assertions match the implementation behavior.
34-37: LGTM — mock forgetUserFriendlyMessageproperly set up.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
process.env coerces undefined to the string "undefined" — use delete instead, wrapped in try/finally for robustness.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/modules/welcome.js`:
- Around line 22-43: The two test-only helpers __getCommunityActivityState and
__resetCommunityActivityState are exported unconditionally; gate their exports
so they are only available in non-production environments (e.g., when
process.env.NODE_ENV !== 'production') or behind a clearly named dev-only flag.
Update the module to conditionally export these functions (or export a single
devHelpers object) based on NODE_ENV, and keep normal runtime exports unchanged
so production consumers cannot call __getCommunityActivityState or
__resetCommunityActivityState.
In `@tests/modules/welcome.test.js`:
- Around line 141-188: The test currently calls vi.restoreAllMocks() and
__resetCommunityActivityState() at the end which won't run if an assertion
throws; ensure cleanup always runs by moving those calls into an afterEach hook
(or wrapping the test body in try/finally) so mocks are restored and community
state reset regardless of failures — update the test that uses
recordCommunityActivity, __getCommunityActivityState, vi.spyOn(Date, 'now'),
vi.restoreAllMocks, and __resetCommunityActivityState to use afterEach cleanup
or try/finally.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/modules/welcome.jstests/modules/welcome.test.js
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.js: Use ESM syntax withimport/export; never userequire()
Always usenode:protocol when importing 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 logging viaimport { info, warn, error } from '../logger.js'— NEVER useconsole.log(),console.warn(),console.error(), or anyconsole.*method in src/ files
Pass structured metadata when logging:info('Message processed', { userId, channelId })
Use custom error classes fromsrc/utils/errors.jsfor error handling
Always log errors with context before re-throwing
UsegetConfig()fromsrc/modules/config.jsto read config; usesetConfigValue(key, value)to update at runtime
UsesplitMessage()utility for messages exceeding Discord's 2000-character limit
No TypeScript — use plain JavaScript with JSDoc comments for documentation
Files:
src/modules/welcome.js
src/modules/*.js
📄 CodeRabbit inference engine (AGENTS.md)
Check
config.yourModule.enabledbefore processing in module handlers
Files:
src/modules/welcome.js
tests/**/*.{js,test.js}
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest for testing with
pnpm test; test coverage provider is@vitest/coverage-v8
Files:
tests/modules/welcome.test.js
🧬 Code graph analysis (1)
tests/modules/welcome.test.js (1)
src/modules/welcome.js (3)
__resetCommunityActivityState(39-43)recordCommunityActivity(66-105)__getCommunityActivityState(27-34)
🔇 Additional comments (4)
src/modules/welcome.js (3)
11-14: LGTM!The throttled eviction constants are well-defined. The module-level counter with a constant interval is a clean approach for periodic pruning.
98-105: LGTM!The periodic eviction trigger is clean — counter increment, threshold check, reset, and prune. The
cutoffvalue reuses the already-computed threshold from the current call, which is correct.
111-123: LGTM!The pruning logic is sound. Since timestamps are appended chronologically, checking only the last element against the cutoff is sufficient to determine staleness. Deleting from a
Mapduringfor...ofiteration is safe per the ES6 specification.tests/modules/welcome.test.js (1)
12-13: LGTM!New test helper imports align with the exports added in the source module.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
… (#83) * feat(dashboard): add Discord entity pickers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(dashboard): add diff view and polish - ConfigDiff.tsx: visual diff component showing before/after config changes with green additions and red deletions using the `diff` library - SystemPromptEditor.tsx: textarea with real-time character count, max length warning indicator, and accessible labeling - Toast notifications via sonner: success/error toasts on save, load failures, and reset actions positioned bottom-right - ResetDefaultsButton with confirmation dialog using Radix UI Dialog - ConfigEditor.tsx: full config editing page with AI, welcome message, and moderation sections; PATCH-based save with diff preview - Config API proxy route (GET/PATCH) following established analytics proxy pattern with guild admin authorization - Dialog UI component (shadcn/ui new-york style) - Added lint script to web/package.json Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * polish(config-editor): improve UX, accessibility, and edge case handling - Replace native checkboxes with styled toggle switches using proper role="switch" and aria-checked attributes - Add unsaved changes guard (beforeunload warning + yellow banner) - Add Ctrl/Cmd+S keyboard shortcut for saving - Block save when system prompt exceeds character limit - Rename misleading "Reset to Defaults" to "Discard Changes" with accurate dialog copy (reverts to last saved state, not factory defaults) - Add diff summary counts (+N / -N) to the pending changes card - Improve accessibility throughout: aria-labels on loading spinner, aria-describedby linking textareas to their hints, aria-invalid on over-limit prompt, aria-live on character counter, aria-hidden on decorative icons, role="region" on diff view - Memoize hasChanges and hasValidationErrors to avoid redundant JSON.stringify on every render - Validate PATCH body shape in API route before proxying upstream - Fix stale "bills-bot" prefix in guild-selection localStorage keys (missed during volvox rename) Closes #31 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(api): add config endpoints for Issue #31 Add REST API endpoints for managing bot configuration: - GET /api/v1/config - Retrieve current config (ai, welcome, spam, moderation) - PUT /api/v1/config - Update config with schema validation Features: - Type-safe schema validation for config sections - Flatten nested objects to dot-notation paths for persistence - requireGlobalAdmin middleware (API-secret or bot-owner OAuth) - Proper HTTP error codes (400 for validation, 401/403 for auth, 500 for errors) - Added PUT to CORS methods Tests: - 35 comprehensive tests covering auth, validation, types, edge cases - Tests for validateConfigSchema and flattenToLeafPaths exports Closes #31 * feat(api): add webhook notifications for config changes - Add notifyDashboardWebhook() fire-and-forget sender to PATCH /:id/config - POST /webhooks/config-update endpoint for dashboard to push config changes - Webhook uses DASHBOARD_WEBHOOK_URL env var with 5s timeout - Add comprehensive tests for webhook functionality * feat(dashboard): add config editor with Zustand state management Add a full config editor UI at /dashboard/config with: - Proxy API routes (GET + PATCH) for bot config at /api/guilds/:guildId/config - Zustand store for config state with fetch, update, and debounced saves - Accordion-based sections for ai, welcome, spam, moderation (read-only), triage - Recursive field renderer supporting booleans, numbers, strings, arrays, objects - shadcn/ui components: accordion, input, label, switch, textarea Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(dashboard): enhance Discord entity pickers with multi-select and Zustand - Add multi-select mode to ChannelSelector and RoleSelector (multiple?: boolean prop) - Create Zustand store for caching channels/roles per guild - Add dedicated bot API endpoints: GET /:id/channels and GET /:id/roles - Add Next.js proxy routes for channels and roles - Update AGENTS.md with new key files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unused import in webhooks.js * fix: resolve all linting errors across codebase - Migrate biome config from 2.3.14 to 2.4.0 - Fix unused imports (triage.js, modAction.test.js) - Fix import ordering (auth.js, lock.js, unlock.js, ai.js, triage-respond.js, modAction.js, modAction.test.js) - Fix formatting across 19 files - Replace O(n²) spread in reduce with push (cli-process.test.js) - Use Object.hasOwn() instead of Object.prototype.hasOwnProperty (config-guild.test.js) All 1310 tests pass. * feat: add full config editing support for moderation and triage - Add moderation and triage to SAFE_CONFIG_KEYS in guilds.js, webhooks.js, and config.js making them writable via PATCH/PUT endpoints - Expand READABLE_CONFIG_KEYS to include all sections: ai, welcome, spam, moderation, triage, logging, memory, permissions - Add CONFIG_SCHEMA definitions for moderation and triage sections with full type validation - Update WritableConfigSection type to include moderation and triage - Remove moderation from READ_ONLY_SECTIONS in config-section.tsx - Update config-store.ts writable keys check - Add editable moderation section in dashboard config-editor with toggles for enabled, autoDelete, DM notifications, and escalation - Add editable triage section with fields for models, budgets, intervals, streaming, debug footer, and moderation log channel - Update all test assertions to reflect new writable sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): add webhook URL validation, schema validation on all write paths, atomic writes - Add shared validateWebhookUrl() utility that blocks SSRF (localhost, private IPs, link-local, IPv6 loopback) and enforces https in production - Wire URL validation into config.js notifyWebhook and guilds.js notifyDashboardWebhook - Export validateSingleValue() from config.js and apply it to the PATCH endpoint in guilds.js and the POST /config-update webhook endpoint - Add path length (<=200 chars) and depth (<=10 segments) limits to guilds.js PATCH and webhooks.js POST endpoints - Refactor PUT handler in config.js to track per-field write results: returns 200 on full success, 207 on partial failure, 500 when all fail - Add comprehensive tests for all new validations and error responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract shared config allowlist, webhook utility, and proxy helpers; remove dead code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(frontend): batch saves, fix race conditions, DRY constants, localStorage safety - Batch saveChanges into parallel PATCH requests grouped by section instead of sequential individual PATCHes (I5) - Add request sequence counter to Zustand config store to prevent stale PATCH responses from clobbering newer state (I6) - Centralize SYSTEM_PROMPT_MAX_LENGTH constant in types/config.ts and import in config-editor and system-prompt-editor (M2) - Wrap localStorage.getItem in try/catch for SSR safety (M3) - Fix channels.length / roles.length truthiness bug — use !== undefined instead of .length which is falsy for 0 (M5) - Replace JSON.stringify deep comparison with recursive deepEqual utility function (M8) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): mask sensitive config fields, block IPv4-mapped IPv6 SSRF, reject unknown config paths - Add SENSITIVE_FIELDS set and maskSensitiveFields utility to strip triage API keys (classifyApiKey, respondApiKey) from all GET config responses - Block SSRF via IPv4-mapped IPv6 addresses (::ffff:127.0.0.1, hex form ::ffff:7f00:1, and cloud metadata ::ffff:169.254.169.254) - Reject unknown config paths in validateSingleValue instead of silently accepting them without type checking - Add cache size limit (100 entries) to webhook URL validation cache - Guard flattenToLeafPaths against __proto__/constructor/prototype keys Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(backend): extract shared validators and getBotOwnerIds, add webhook utility tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(frontend): remove dead code, fix save flow, harden inputs and type guards - C4: delete 10 unused files (stores, UI components, dashboard selectors) and remove zustand, @radix-ui/react-accordion, @radix-ui/react-label, @radix-ui/react-switch from package.json - m8: replace ~85-line local GuildConfig interface with DeepPartial<BotConfig> (add DeepPartial utility to types/config.ts) - m4: harden isGuildConfig type guard to verify at least one expected section key (ai, welcome, spam) instead of just typeof === "object" - M6: fix computePatches to include top-level paths (remove incorrect fullPath.includes(".") guard that silently dropped top-level field changes) - M7: fix partial save to merge only succeeded sections into savedConfig on partial failure, preserving draft edits for failed sections; only call fetchConfig() on full success - m5: add min constraints to triage number inputs (budgets min=0, timeouts min=1, buffer/context sizes min=1) - m9: add e.returnValue = "" to beforeunload handler for modern browser support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: fix 10-segment test to use valid schema path after strict validation * fix: wire default-personality.md as system prompt fallback Replace generic 'You are a helpful Discord bot.' fallback with the existing default-personality.md template, which provides the intended Volvox Bot personality, role constraints, and anti-abuse guardrails. * fix: merge default-personality into responder system prompt, remove dead template Merge personality, role, constraints, and anti-abuse from the unused default-personality.md into triage-respond-system.md (the responder's actual system prompt). Revert the fallback wiring in triage-prompt.js since personality now lives in the system prompt file where it belongs. Delete default-personality.md — no longer needed. * 📝 Add docstrings to `feat/config-editor-combined` Docstrings generation was requested by @BillChirico. The following files were modified: * `src/api/routes/auth.js` * `src/api/routes/config.js` * `src/api/routes/guilds.js` * `src/api/utils/configAllowlist.js` * `src/api/utils/validateConfigPatch.js` * `src/api/utils/validateWebhookUrl.js` * `src/commands/lock.js` * `src/commands/slowmode.js` * `src/commands/unlock.js` * `src/modules/memory.js` * `src/modules/triage-prompt.js` * `src/modules/triage-respond.js` * `src/modules/triage.js` * `src/utils/debugFooter.js` * `src/utils/permissions.js` * `web/src/app/api/guilds/[guildId]/channels/route.ts` * `web/src/app/api/guilds/[guildId]/config/route.ts` * `web/src/app/api/guilds/[guildId]/roles/route.ts` * `web/src/app/dashboard/config/page.tsx` * `web/src/components/dashboard/config-diff.tsx` * `web/src/components/dashboard/config-editor.tsx` * `web/src/components/dashboard/reset-defaults-button.tsx` * `web/src/components/dashboard/system-prompt-editor.tsx` * `web/src/components/providers.tsx` * `web/src/lib/bot-api-proxy.ts` These files were kept as they were: * `src/api/server.js` * `src/api/utils/webhook.js` These files were ignored: * `tests/api/routes/config.test.js` * `tests/api/routes/guilds.test.js` * `tests/api/routes/webhooks.test.js` * `tests/api/utils/validateWebhookUrl.test.js` * `tests/api/utils/webhook.test.js` * `tests/commands/tempban.test.js` * `tests/config-listeners.test.js` * `tests/modules/cli-process.test.js` * `tests/modules/config-guild.test.js` * `tests/utils/modAction.test.js` These file types are not supported: * `.env.example` * `AGENTS.md` * `biome.json` * `src/prompts/triage-respond-system.md` * `web/package.json` * fix(prompts): replace ambiguous 'classified' with 'triaged' (Thread #11) * fix(prompts): define concrete 'flagging' mechanism for moderation (Thread #12) * fix(prompts): add PII/credentials constraint to prevent echoing secrets (Thread #13) * fix: remove bracketed-IPv6 dead code in webhook URL validator URL.hostname already strips brackets from IPv6 addresses (new URL('http://[::1]').hostname === '::1'), so the hostname.startsWith('[') branch was unreachable dead code. Remove the bracketed-IPv6 branch and the '[::1]' entry from BLOCKED_IPV6 since it could never match. Addresses CodeRabbit review thread #9. * fix: sanitize webhook URL in warning logs to prevent credential exposure Strip query string and fragment from webhook URLs before including them in warning log messages. If a webhook URL contains tokens or API keys as query parameters, they would previously appear in logs. Now logs only origin + pathname (e.g. 'https://example.com/hook') instead of the full URL with sensitive query params. Addresses CodeRabbit review thread #10. * test(config): rename misleading 'Infinity' test name (Thread #14) * fix: normalize validateSingleValue to always return string[] Unknown config paths previously returned [{path, message}] objects while validateValue returned string[]. Callers now receive a consistent string[] in both cases, eliminating the need to handle two different shapes. * test(config): move PUT partial write handling inside config routes suite (Thread #15) * refactor: extract getGuildChannels helper to remove duplication GET /:id and GET /:id/channels shared identical channel-fetching loops with a duplicated MAX_CHANNELS constant. Extracted into a single getGuildChannels(guild) helper used by both handlers. * feat: include section (topLevelKey) in PATCH config webhook payload Downstream consumers of the DASHBOARD_WEBHOOK_URL can now use the 'section' field to selectively reload only the affected config section rather than refreshing the entire config. * fix: return promise in webhook config-update handler (async/await) The previous .then()/.catch() chain was not returned, so Express 5 could not auto-forward rejected promises to the error handler. Converted to async/await so errors propagate correctly. * feat: make JSON body size limit configurable via API_BODY_LIMIT env var Defaults to '100kb' when the env var is not set, preserving existing behaviour. Operators can now tune the limit without code changes. * test(validateWebhookUrl): strengthen cache eviction test to verify re-evaluation (Thread #16) * test(webhook): move vi.useRealTimers() to afterEach to prevent timer leak (Thread #17) * refactor: move CONFIG_SCHEMA/validateValue/validateSingleValue to utils/configValidation.js validateConfigPatch.js (utils) was importing validateSingleValue from routes/config.js — an inverted dependency. Created src/api/utils/configValidation.js as the canonical home for CONFIG_SCHEMA, validateValue, and validateSingleValue. - config.js now imports from ../utils/configValidation.js and re-exports validateSingleValue for backward compatibility with existing callers. - validateConfigPatch.js now imports from ./configValidation.js directly. * perf: split path string once in validateConfigPatchBody path.split('.') was called twice — once to extract topLevelKey and again for segments. Moved the single split to the top and derived topLevelKey from segments[0], avoiding the redundant allocation. * fix(#18): change lint script from tsc to biome check * fix(#19,#20): simplify params type; add PATCH body value check * fix(#21): add metadata export to config page * fix(#22): compute addedCount/removedCount inside useMemo loop * fix(#23,#24,#25,#26): tighten isGuildConfig; extract inputClasses; guard number inputs; rename DiscardChangesButton * fix(#27): change aria-live from polite to off on char counter * fix(#28): change Toaster theme from dark to system * fix(#29,#30): export BotApiConfig; return 504 on AbortError/TimeoutError * fix(#31): add one-time localStorage key migration from old key * fix(#32,#33,#34): SpamConfig JSDoc; collapse WritableConfigSection; fix SYSTEM_PROMPT_MAX_LENGTH JSDoc * fix: remove unused validateSingleValue import, fix biome formatting in config.js After the refactor, validateSingleValue is re-exported directly via 'export { } from' and no longer needs a local import binding. Also removed an extra blank line that biome flagged as a format error. * fix: add DNS resolution validation to prevent SSRF via DNS rebinding Add async validateDnsResolution() that resolves a webhook hostname via DNS and checks all resolved IPs against the existing blocked ranges before fetch. This closes the TOCTOU gap where a hostname could pass string-based validation then resolve to a private IP at request time (DNS rebinding attack). Changes: - Add validateDnsResolution() with resolve4/resolve6 checks - Integrate DNS check in fireAndForgetWebhook before fetch - Normalize IPv6 hostnames by stripping brackets (Node.js v22 retains them in URL.hostname, contrary to WHATWG spec) - Add comprehensive test coverage for DNS rebinding scenarios - Update webhook tests for async DNS validation flow Addresses CodeRabbit review thread #8. * fix: update test assertions for string[] return type from validateSingleValue * fix: mock validateDnsResolution in webhook integration tests After adding DNS resolution pinning in fireAndForgetWebhook, the config and guilds route tests need to mock validateDnsResolution to return true so fetch is actually called. * fix: address minor code review feedback - JSDoc, tests, caps * fix(frontend): address code review feedback - HTML, types, perf * fix(backend): address code review feedback - validation, logging, exports * fix: correct IPv6 validation for public addresses and literals * fix: restore classifier invocation in triage module * fix: address test failures in validateConfigPatch and triage-respond - Check for empty path segments before topLevelKey validation - Fix test to use valid nullable path (welcome.channelId) - Add mock cleanup between triage-respond tests * fix(validation): handle alertChannelId nullable and DNS edge cases * fix(security): prevent mask sentinel write-back and auth secret override 1. configAllowlist: Add isMasked() and stripMaskedWrites() to detect and filter out writes where sensitive fields contain the mask sentinel ('••••••••'). Prevents clients from accidentally overwriting real secrets with the placeholder returned by maskSensitiveFields(). 2. bot-api-proxy: Reorder header spread so x-api-secret is always set AFTER spreading options.headers, preventing caller-provided headers from overriding the server-side auth secret. Both fixes include comprehensive tests. * test: add missing test cases for mask sentinel, prototype pollution, DNS edge cases * refactor: simplify webhook validation for internal-only use * refactor: remove unused SSRF validation code Deleted validateWebhookUrl.js and its tests since webhooks are internal-only. Simplified webhook.js to just check URL format. * fix: prevent WebSearch notification failures from aborting response * fix: correct safeSend mock setup in triage-respond tests * fix(security): use own-property checks in config validation * fix: export MASK constant and clean up orphaned JSDoc * fix: report written sections in webhook, add roles endpoint test * fix: address remaining PR review feedback - Add nullable: true to triage.moderationLogChannel and debugFooterLevel - Add evalClient param to runResponder JSDoc - Convert SAFE_CONFIG_KEYS to Set for O(1) lookups - Reorder validation checks for consistent 400 responses - Update tests for Set-based SAFE_CONFIG_KEYS --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: AnExiledDev <AnExiledDev@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary
messageCreateAI-response block in try-catch with agetUserFriendlyMessage()fallback so Discord API failures (rate limits, missing permissions, deleted channels) no longer surface as unhandled rejectionsNODE_ENVcheck inconfig.jsso error details only leak when explicitly set todevelopment, not wheneverproductionis unsetunhandledRejectionhandler — Removed the handler fromindex.js; the canonical one inevents.jsremainsregisterPendingRequest/removePendingRequestand the unused pending-request wait loop from graceful shutdownbuf.abortControllerin thefinallyblock so it doesn't linger until the nextresetCounter()pruneStaleActivity()every 50 calls torecordCommunityActivity()so stale guild data doesn't accumulate indefinitelyTest plan
npm test)