Skip to content

RG-T117 Chat System and In-App Chatbot - #444

Merged
ucswift merged 5 commits into
masterfrom
develop
Aug 1, 2026
Merged

RG-T117 Chat System and In-App Chatbot#444
ucswift merged 5 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 31, 2026

Copy link
Copy Markdown
Member

Pull Request Description

RG-T117: Realtime Chat System and In-App Chatbot

This pull request introduces a comprehensive real-time chat system and integrates the existing chatbot into a web-based chat interface for the Resgrid platform.

Chat System

The core feature is a full-featured real-time chat system supporting:

  • Channel types: Direct messages, ad-hoc groups, department-wide, group-specific, permission-locked custom channels, and incident-specific channels (call, lane, and command) that are automatically provisioned and archived alongside calls
  • Messaging: Text, images, GIFs, and location sharing with threading, urgent priority messages requiring acknowledgment, emoji reactions, pinning, editing (with audit history), and tombstone deletes
  • Delivery: REST-first writes with client idempotency, per-channel monotonic message sequences, keyset pagination, and delta sync on reconnect via SignalR
  • Real-time fan-out: Message events flow through the RabbitMQ eventing topic to SignalR-connected clients across all web and mobile apps
  • Push notifications: Per-recipient notification preferences (All, MentionsOnly, Muted) with mention and urgent-override support, delivered via Novu

Chatbot Web Integration

The existing SMS-based chatbot is now accessible as an in-app AI assistant conversation channel. A guard-railed conversational LLM fallback answers informational questions when no intent matches, while operational commands remain exclusively with the deterministic handlers. Bot replies are persisted into a per-user chatbot channel and delivered over SignalR.

Moderation and Administration

Department admins gain a moderation console with flag review, message deletion, user muting/banning, channel locking, per-department chat settings (retention, content toggles), and transcript export (JSON/CSV/ZIP) for records requests.

Infrastructure

  • Database migrations (SQL Server and PostgreSQL) for all chat tables with filtered unique indexes for deduplication
  • Nightly retention purge and export processing background workers
  • A Chat.System feature flag (seeded off) gates the entire system for staged rollout
  • Push notification integration via Novu chat workflows for user, IC, and unit-device subscribers

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 153 files, which is 53 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e3aa8bc9-38f1-4fb4-b1d6-d69ef72cfea0

📥 Commits

Reviewing files that changed from the base of the PR and between 63f697b and dd160c0.

⛔ Files ignored due to path filters (15)
  • Core/Resgrid.Localization/Common.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.uk.resx is excluded by !**/*.resx
  • Tests/Resgrid.Tests/Chatbot/ChatbotTextResponseResolverTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Repositories/MongoRepositoryConfigurationTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/ChatPermissionServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/QueueServiceTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (153)
  • Core/Resgrid.Chatbot.NLU/LlmEndpointValidator.cs
  • Core/Resgrid.Chatbot.NLU/NLUModule.cs
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleNluProvider.cs
  • Core/Resgrid.Chatbot/ChatbotModule.cs
  • Core/Resgrid.Chatbot/Interfaces/IChatCompletionClient.cs
  • Core/Resgrid.Chatbot/Interfaces/IChatbotWebChatNotifier.cs
  • Core/Resgrid.Chatbot/Services/ChatWebChatNotifier.cs
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs
  • Core/Resgrid.Chatbot/Services/ConversationalFallbackService.cs
  • Core/Resgrid.Chatbot/Services/NullChatbotWebChatNotifier.cs
  • Core/Resgrid.Config/ChatConfig.cs
  • Core/Resgrid.Config/DataConfig.cs
  • Core/Resgrid.Framework/Logging.cs
  • Core/Resgrid.Model/AuditLogTypes.cs
  • Core/Resgrid.Model/Chat/ChatChannel.cs
  • Core/Resgrid.Model/Chat/ChatEnums.cs
  • Core/Resgrid.Model/Chat/ChatInteractions.cs
  • Core/Resgrid.Model/Chat/ChatMessage.cs
  • Core/Resgrid.Model/Chat/ChatModeration.cs
  • Core/Resgrid.Model/EventingTypes.cs
  • Core/Resgrid.Model/Events/ChatEvents.cs
  • Core/Resgrid.Model/FeatureFlagKeys.cs
  • Core/Resgrid.Model/Providers/IGifProvider.cs
  • Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs
  • Core/Resgrid.Model/Providers/Models/INovuProvider.cs
  • Core/Resgrid.Model/Repositories/IChatRepositories.cs
  • Core/Resgrid.Model/Repositories/IMongoRepository.cs
  • Core/Resgrid.Model/Repositories/IPersonnelLocationsDocRepository.cs
  • Core/Resgrid.Model/Repositories/IUnitLocationsDocRepository.cs
  • Core/Resgrid.Model/Repositories/IUnitLocationsMongoRepository.cs
  • Core/Resgrid.Model/Services/IChatServices.cs
  • Core/Resgrid.Model/Services/IDepartmentsService.cs
  • Core/Resgrid.Model/Services/IPushService.cs
  • Core/Resgrid.Model/Services/IUsersService.cs
  • Core/Resgrid.Services/AuditService.cs
  • Core/Resgrid.Services/ChatChannelService.cs
  • Core/Resgrid.Services/ChatMessageService.cs
  • Core/Resgrid.Services/ChatModerationService.cs
  • Core/Resgrid.Services/ChatNotificationService.cs
  • Core/Resgrid.Services/ChatPermissionService.cs
  • Core/Resgrid.Services/ChatPresenceService.cs
  • Core/Resgrid.Services/ChatProvisioningEventService.cs
  • Core/Resgrid.Services/DepartmentsService.cs
  • Core/Resgrid.Services/IncidentCommandService.cs
  • Core/Resgrid.Services/PushService.cs
  • Core/Resgrid.Services/QueueService.cs
  • Core/Resgrid.Services/ServicesModule.cs
  • Core/Resgrid.Services/UnitsService.cs
  • Core/Resgrid.Services/UsersService.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs
  • Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs
  • Providers/Resgrid.Providers.Messaging/MessagingProviderModule.cs
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0105_AddChatMessages.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0106_AddChatInteractions.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0107_AddChatModeration.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0108_SeedChatFeatureFlag.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0109_AddChatHotPathIndexes.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0105_AddChatMessagesPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0106_AddChatInteractionsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0107_AddChatModerationPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0108_SeedChatFeatureFlagPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0109_AddChatHotPathIndexesPg.cs
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/ApiDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/NonWebDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/MongoClientFactory.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/MongoRepository.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/PersonnelLocationsDocRepository.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsMongoRepository.cs
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs
  • Web/Resgrid.Web.Eventing/Startup.cs
  • Web/Resgrid.Web.Eventing/Worker.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/UnitTrackingIngressController.cs
  • Web/Resgrid.Web.Services/Hubs/CommunicationHub.cs
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Services/Startup.cs
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChannelList.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatModerationElement.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatPageElement.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatPanelElement.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/FlagDialog.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/MembersPanel.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/NewConversationDialog.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/PinsPanel.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/AttachmentImage.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Avatar.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Dialog.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Lightbox.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/MessageBubble.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/ReactionChips.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/StatusBanners.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/TypingRow.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/emoji.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/usePopoverClose.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chat.css
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatFormat.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ActionsTab.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ExportsTab.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/SettingsTab.tsx
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/types.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/useChatBootstrap.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/useChatStore.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/elements.ts
  • Web/Resgrid.Web/Areas/User/Apps/src/runtime/api.ts
  • Web/Resgrid.Web/Areas/User/Controllers/ChatController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs
  • Web/Resgrid.Web/Areas/User/Views/Chat/Chatbot.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Chat/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Chat/Moderation.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/Chat.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_Navigation.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_UserLayout.cshtml
  • Web/Resgrid.Web/wwwroot/_references.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/messages/resgrid.messages.chat.js
  • Web/Resgrid.Web/wwwroot/js/ng/react-elements.css
  • Web/Resgrid.Web/wwwroot/js/ng/react-elements.js
  • Workers/Resgrid.Workers.Console/Commands/ChatExportCommand.cs
  • Workers/Resgrid.Workers.Console/Commands/ChatRetentionCommand.cs
  • Workers/Resgrid.Workers.Console/Program.cs
  • Workers/Resgrid.Workers.Console/Tasks/ChatExportTask.cs
  • Workers/Resgrid.Workers.Console/Tasks/ChatRetentionTask.cs
  • Workers/Resgrid.Workers.Console/Tasks/PersonnelLocationQueuesProcessorTask.cs
  • Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs
  • Workers/Resgrid.Workers.Framework/Logic/ChatExportLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/ChatRetentionLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/ChatbotMessageLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/PersonnelLocationQueueLogic.cs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

@Resgrid-Bot

This comment has been minimized.

@request-info

request-info Bot commented Jul 31, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

Comment on lines +77 to +80
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
Content = new StringContent(JsonConvert.SerializeObject(requestBody), Encoding.UTF8, "application/json")
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: HttpRequestMessage implements IDisposable but is never disposed (no using). WHY: Undisposed request messages leak unmanaged resources and socket buffers, especially under load. HOW: Wrap in using var request = new HttpRequestMessage(...) (or a using block) so it is deterministically disposed after SendAsync.

Kody rule violation: Use using statements for disposable resources

Prompt for LLM

File Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:

Line 77 to 80:

WHAT: HttpRequestMessage implements IDisposable but is never disposed (no using). WHY: Undisposed request messages leak unmanaged resources and socket buffers, especially under load. HOW: Wrap in `using var request = new HttpRequestMessage(...)` (or a using block) so it is deterministically disposed after SendAsync.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
catch (Exception ex)
{
Logging.LogException(ex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The catch block logs only the raw exception without any operation context or identifiers. WHY: Rule 3 requires error logs to include the operation name and relevant identifiers (e.g., userId, text snippet) as structured fields so failures are traceable and diagnosable. Logging just ex is analogous to console.error(e) — it loses the context of which user or operation failed. HOW: Pass contextual fields alongside the exception, e.g. Logging.LogException(ex, $"PushToUserAsync failed for userId={userId}"); or use a structured logging overload that accepts a message template with operation name and userId parameters.

Also found in:

  • Core/Resgrid.Services/IncidentCommandService.cs:1486-1486
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/useChatBootstrap.ts:29-29
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:42-42
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:108-108
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/SettingsTab.tsx:56-56
  • Core/Resgrid.Services/IncidentCommandService.cs:1516-1516
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:383-383
  • Web/Resgrid.Web.Eventing/Worker.cs:226-226
  • Workers/Resgrid.Workers.Framework/Logic/ChatRetentionLogic.cs:82-82
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:29-29
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:62-62
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:125-125
  • Core/Resgrid.Services/PushService.cs:247-247
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:89-89
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:58-58
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:106-106
  • Core/Resgrid.Services/ChatMessageService.cs:183-183
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:59-59
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ExportsTab.tsx:35-35
  • Core/Resgrid.Services/PushService.cs:260-260
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:58-58
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:53-53
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:198-198
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:408-408
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:105-105
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx:54-54
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:72-72
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:105-105
  • Core/Resgrid.Services/PushService.cs:277-277
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx:73-73
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx:82-82
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:129-129
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/MembersPanel.tsx:48-48
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:118-118
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:153-153
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1150-1150
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:161-161
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1197-1197
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:775-775
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1504-1504
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1549-1549
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1447-1447
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:381-381
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:669-669
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:138-138
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1291-1291
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:456-456
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1103-1103
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1336-1336
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:940-940
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:170-170
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:119-119
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:238-238
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1246-1246
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:273-273
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:612-612
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:254-254
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:581-581
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:895-895
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1005-1005
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:700-700
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1621-1621
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:216-216
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:336-336
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:743-743
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1058-1058
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1367-1367
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:210-210
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:549-549
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:805-805
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:179-179
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:306-306
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:487-487
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:518-518
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:411-411
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:89-89
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1399-1399
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:864-864
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1591-1591
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:242-242
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:149-149

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Core/Resgrid.Chatbot/Services/ChatWebChatNotifier.cs:

Line 53:

WHAT: The catch block logs only the raw exception without any operation context or identifiers. WHY: Rule 3 requires error logs to include the operation name and relevant identifiers (e.g., userId, text snippet) as structured fields so failures are traceable and diagnosable. Logging just `ex` is analogous to `console.error(e)` — it loses the context of which user or operation failed. HOW: Pass contextual fields alongside the exception, e.g. `Logging.LogException(ex, $"PushToUserAsync failed for userId={userId}");` or use a structured logging overload that accepts a message template with operation name and userId parameters.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// Unknown intent: try the guard-railed conversational LLM fallback before giving up.
// Operational commands never reach here — matched intents dispatched above — so the
// fallback can only produce informational replies.
var fallbackResponse = await _conversationalFallback.TryHandleAsync(message, session);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The new conversational LLM fallback is awaited without any try/catch. WHY: TryHandleAsync is an external/network dependency (an LLM service); an exception, timeout, or transient failure would propagate and crash the entire ProcessMessageAsync flow, losing the user's message. HOW: Wrap the call in try/catch, log structured context (intent, session id, message id), and fall through to the existing "I didn't understand" response on failure rather than letting the exception escape.

Also found in:

  • Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs:603-603
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs:123-123
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:318-318
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:134-134
  • Core/Resgrid.Services/ChatChannelService.cs:179-179
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/PinsPanel.tsx:48-48
  • Core/Resgrid.Services/ChatChannelService.cs:207-207
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:50-50
  • Core/Resgrid.Services/ChatChannelService.cs:335-335
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:257-257
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:146-146
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:41-41
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:45-45
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:100-100
  • Core/Resgrid.Services/ChatPresenceService.cs:27-27
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:385-385
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/MembersPanel.tsx:22-22
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:120-120
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:503-503
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:33-33
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:160-160
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:57-57
  • Core/Resgrid.Services/PushService.cs:234-234
  • Core/Resgrid.Services/PushService.cs:239-239
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:45-45
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:101-101
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:429-429
  • Core/Resgrid.Services/ChatPresenceService.cs:41-41
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:61-61
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:109-109
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:262-262
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:133-133
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:228-228
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:194-194
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:48-48
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:124-124
  • Core/Resgrid.Services/ChatPresenceService.cs:29-29
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:57-57
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:79-79
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:133-133
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:61-61
  • Core/Resgrid.Services/ChatPresenceService.cs:36-36
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:102-102
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:109-109
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:124-124
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:84-84
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:106-106
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:121-121
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:84-84
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:106-106
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:121-121
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:79-79
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:102-102
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:117-117
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:117-117

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs:

Line 558:

WHAT: The new conversational LLM fallback is awaited without any try/catch. WHY: TryHandleAsync is an external/network dependency (an LLM service); an exception, timeout, or transient failure would propagate and crash the entire ProcessMessageAsync flow, losing the user's message. HOW: Wrap the call in try/catch, log structured context (intent, session id, message id), and fall through to the existing "I didn't understand" response on failure rather than letting the exception escape.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public object IdValue
{
get { return ChatChannelId; }
set { ChatChannelId = (string)value; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Violates team rule 'Use safe type casting with as operator': Use the as operator or pattern matching for safe casts and guard null results before usage.

Also found in:

  • Core/Resgrid.Model/Chat/ChatChannel.cs:133-133
  • Core/Resgrid.Model/Chat/ChatChannel.cs:209-209
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:45-45
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:88-88
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:133-133
  • Core/Resgrid.Model/Chat/ChatMessage.cs:90-90
  • Core/Resgrid.Model/Chat/ChatMessage.cs:131-131
  • Core/Resgrid.Model/Chat/ChatMessage.cs:186-186
  • Core/Resgrid.Model/Chat/ChatModeration.cs:51-51
  • Core/Resgrid.Model/Chat/ChatModeration.cs:101-101
  • Core/Resgrid.Model/Chat/ChatModeration.cs:147-147
  • Core/Resgrid.Model/Chat/ChatModeration.cs:199-199
  • Core/Resgrid.Model/Events/ChatEvents.cs:5-5
  • Core/Resgrid.Model/Services/IChatServices.cs:130-130
  • Core/Resgrid.Services/ChatChannelService.cs:96-96
  • Core/Resgrid.Services/ChatChannelService.cs:254-254
  • Core/Resgrid.Services/ChatChannelService.cs:550-550
  • Core/Resgrid.Services/ChatMessageService.cs:342-342
  • Core/Resgrid.Services/ChatModerationService.cs:155-155
  • Core/Resgrid.Services/ChatModerationService.cs:177-177
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:85-85
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:86-86
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:87-87
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:88-88
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:110-110
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:111-111
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:112-112
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:113-113
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:56-56
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:62-62
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:65-65
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:285-285
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:106-106
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:155-155
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:156-156
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:224-224
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:237-237
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:255-255
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:306-306
  • Tests/Resgrid.Tests/Services/ChatPermissionServiceTests.cs:59-59
  • Tests/Resgrid.Tests/Services/ChatPermissionServiceTests.cs:170-170
  • Tests/Resgrid.Tests/Services/ChatPermissionServiceTests.cs:296-296
  • Tests/Resgrid.Tests/Services/ChatPermissionServiceTests.cs:768-768
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:709-709
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:710-710
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1350-1350
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:74-74
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:120-120
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:429-429
  • Workers/Resgrid.Workers.Console/Tasks/ChatExportTask.cs:39-39
  • Workers/Resgrid.Workers.Console/Tasks/ChatRetentionTask.cs:39-39
  • Workers/Resgrid.Workers.Framework/Logic/ChatExportLogic.cs:70-70
  • Workers/Resgrid.Workers.Framework/Logic/ChatRetentionLogic.cs:76-76
  • Workers/Resgrid.Workers.Framework/Logic/ChatbotMessageLogic.cs:55-55
Prompt for LLM

File Core/Resgrid.Model/Chat/ChatChannel.cs:

Line 87:

Violates team rule 'Use safe type casting with as operator': Use the as operator or pattern matching for safe casts and guard null results before usage.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public string PayloadJson { get; set; }

/// <summary>Target a single user's devices instead of the channel group (chatbot, DM invites, badges).</summary>
public string TargetUserId { get; set; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The auto-property TargetUserId has no default value. WHY: Rule 32 requires properties to be initialized with sensible defaults; a bare string property defaults to null, which could be mistaken for a valid 'no target' state. HOW: Initialize with string.Empty (e.g., public string TargetUserId { get; set; } = string.Empty;) or explicitly mark as nullable to make intent clear.

Also found in:

  • Workers/Resgrid.Workers.Console/Commands/ChatExportCommand.cs:16-16
  • Core/Resgrid.Model/Events/ChatEvents.cs:15-15
  • Core/Resgrid.Model/Events/ChatEvents.cs:12-12
  • Core/Resgrid.Model/Events/ChatEvents.cs:21-21
  • Workers/Resgrid.Workers.Console/Commands/ChatRetentionCommand.cs:16-16
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:11-11
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:428-428
  • Core/Resgrid.Model/Chat/ChatModeration.cs:11-11
  • Core/Resgrid.Model/Chat/ChatChannel.cs:16-16
  • Core/Resgrid.Model/Chat/ChatMessage.cs:15-15
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:427-427
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:35-35
  • Core/Resgrid.Chatbot/Interfaces/IChatCompletionClient.cs:9-9
  • Core/Resgrid.Model/Providers/IGifProvider.cs:9-9
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:948-948
  • Core/Resgrid.Model/Chat/ChatMessage.cs:155-155
  • Core/Resgrid.Model/Services/IChatServices.cs:204-204
  • Core/Resgrid.Chatbot/Interfaces/IChatCompletionClient.cs:10-10
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:969-969
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1089-1089
  • Core/Resgrid.Model/Chat/ChatModeration.cs:35-35
  • Core/Resgrid.Model/Chat/ChatModeration.cs:85-85
  • Core/Resgrid.Model/Chat/ChatModeration.cs:83-83
  • Core/Resgrid.Model/Chat/ChatModeration.cs:24-24
  • Core/Resgrid.Model/Chat/ChatModeration.cs:183-183
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1022-1022
  • Core/Resgrid.Model/Services/IChatServices.cs:183-183
  • Core/Resgrid.Model/Services/IChatServices.cs:185-185
  • Core/Resgrid.Model/Services/IChatServices.cs:190-190
  • Core/Resgrid.Model/Services/IChatServices.cs:193-193
  • Core/Resgrid.Model/Services/IChatServices.cs:196-196
  • Core/Resgrid.Model/Services/IChatServices.cs:198-198
  • Core/Resgrid.Model/Services/IChatServices.cs:200-200
  • Core/Resgrid.Model/Chat/ChatModeration.cs:31-31
  • Core/Resgrid.Model/Chat/ChatChannel.cs:25-25
  • Core/Resgrid.Model/Chat/ChatMessage.cs:37-37
  • Core/Resgrid.Model/Chat/ChatMessage.cs:108-108
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:65-65
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:152-152
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:220-220
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:231-231
  • Core/Resgrid.Model/Chat/ChatModeration.cs:64-64
  • Core/Resgrid.Model/Chat/ChatModeration.cs:111-111
  • Core/Resgrid.Model/Chat/ChatModeration.cs:157-157
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:253-253
  • Core/Resgrid.Model/Providers/IGifProvider.cs:12-12
  • Core/Resgrid.Model/Chat/ChatMessage.cs:100-100
  • Core/Resgrid.Model/Chat/ChatMessage.cs:145-145
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:13-13
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:15-15
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:57-57
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:59-59
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:104-104
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:106-106
  • Core/Resgrid.Model/Chat/ChatMessage.cs:168-168
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:66-66
  • Core/Resgrid.Model/Chat/ChatMessage.cs:153-153
  • Core/Resgrid.Model/Providers/IGifProvider.cs:14-14
  • Core/Resgrid.Model/Providers/IGifProvider.cs:10-10
  • Core/Resgrid.Model/Chat/ChatMessage.cs:113-113
  • Core/Resgrid.Model/Chat/ChatModeration.cs:13-13
  • Core/Resgrid.Model/Chat/ChatModeration.cs:15-15
  • Core/Resgrid.Model/Chat/ChatModeration.cs:19-19
  • Core/Resgrid.Model/Chat/ChatModeration.cs:68-68
  • Core/Resgrid.Model/Chat/ChatModeration.cs:70-70
  • Core/Resgrid.Model/Chat/ChatModeration.cs:72-72
  • Core/Resgrid.Model/Chat/ChatModeration.cs:79-79
  • Core/Resgrid.Model/Chat/ChatModeration.cs:161-161
  • Core/Resgrid.Model/Chat/ChatModeration.cs:166-166
  • Core/Resgrid.Model/Chat/ChatChannel.cs:23-23
  • Core/Resgrid.Model/Chat/ChatChannel.cs:27-27
  • Core/Resgrid.Model/Chat/ChatChannel.cs:38-38
  • Core/Resgrid.Model/Chat/ChatChannel.cs:41-41
  • Core/Resgrid.Model/Chat/ChatChannel.cs:44-44
  • Core/Resgrid.Model/Chat/ChatChannel.cs:50-50
  • Core/Resgrid.Model/Chat/ChatChannel.cs:59-59
  • Core/Resgrid.Model/Chat/ChatChannel.cs:100-100
  • Core/Resgrid.Model/Chat/ChatChannel.cs:102-102
  • Core/Resgrid.Model/Chat/ChatChannel.cs:113-113
  • Core/Resgrid.Model/Chat/ChatChannel.cs:115-115
  • Core/Resgrid.Model/Chat/ChatChannel.cs:148-148
  • Core/Resgrid.Model/Chat/ChatChannel.cs:150-150
  • Core/Resgrid.Model/Chat/ChatChannel.cs:157-157
  • Core/Resgrid.Model/Chat/ChatChannel.cs:162-162
  • Core/Resgrid.Model/Chat/ChatChannel.cs:168-168
  • Core/Resgrid.Model/Chat/ChatChannel.cs:188-188
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:22-22
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:110-110
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:27-27
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:55-55
  • Core/Resgrid.Model/Chat/ChatMessage.cs:17-17
  • Core/Resgrid.Model/Chat/ChatInteractions.cs:102-102
  • Core/Resgrid.Model/Chat/ChatMessage.cs:102-102
  • Core/Resgrid.Model/Chat/ChatMessage.cs:104-104
  • Core/Resgrid.Model/Chat/ChatMessage.cs:147-147
  • Core/Resgrid.Model/Chat/ChatMessage.cs:149-149
  • Core/Resgrid.Model/Chat/ChatMessage.cs:160-160
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:268-268
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:278-278
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:283-283
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:298-298
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:303-303
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:354-354
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:359-359
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:379-379
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:389-389
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:394-394
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:409-409
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:429-429
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:434-434
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:454-454
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:464-464
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:494-494
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:499-499
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:504-504
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:520-520
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:530-530
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:546-546
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:551-551
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:561-561
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:571-571
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:627-627
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:632-632
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:637-637
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:642-642
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:668-668
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:673-673
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:678-678
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:683-683
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:693-693
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:708-708
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:718-718
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:729-729
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:734-734
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:739-739
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:744-744
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:759-759
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:769-769
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:774-774
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:785-785
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:831-831
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:836-836
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:841-841
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:876-876
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:887-887
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:892-892
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:897-897
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:902-902
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:927-927
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:943-943
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:959-959
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:964-964
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:995-995
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1006-1006
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1011-1011
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1044-1044
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1049-1049
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1074-1074
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1084-1084
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1105-1105
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1131-1131
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1142-1142
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1174-1174
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1190-1190
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1201-1201
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1217-1217
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1238-1238
  • Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:1290-1290

Kody rule violation: Initialize properties with default values

Prompt for LLM

File Core/Resgrid.Model/Events/ChatEvents.cs:

Line 24:

WHAT: The auto-property `TargetUserId` has no default value. WHY: Rule 32 requires properties to be initialized with sensible defaults; a bare string property defaults to null, which could be mistaken for a valid 'no target' state. HOW: Initialize with `string.Empty` (e.g., `public string TargetUserId { get; set; } = string.Empty;`) or explicitly mark as nullable to make intent clear.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

/// flags) — the retention purge. Moderation action rows are audit and are NOT touched. Returns
/// the number of messages removed.
/// </summary>
Task<int> DeleteMessagesByIdsAsync(List<string> chatMessageIds, CancellationToken cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The parameter type requires a mutable List in a public interface method. WHY: Forcing callers to supply List is unnecessarily restrictive and inconsistent with sibling methods (e.g., lines 24, 82, 113, 118) that accept IEnumerable; IReadOnlyList or IEnumerable is more appropriate for a read-only input. HOW: Change the parameter type to IReadOnlyList or IEnumerable.

Also found in:

  • Core/Resgrid.Model/Repositories/IChatRepositories.cs:92-92
  • Core/Resgrid.Services/ChatModerationService.cs:76-76
  • Core/Resgrid.Services/ChatChannelService.cs:55-55
  • Core/Resgrid.Services/ChatChannelService.cs:265-265
  • Core/Resgrid.Model/Providers/IGifProvider.cs:28-28
  • Core/Resgrid.Services/ChatModerationService.cs:190-190
  • Core/Resgrid.Services/ChatChannelService.cs:271-271
  • Core/Resgrid.Services/ChatModerationService.cs:219-219
  • Core/Resgrid.Model/Providers/IGifProvider.cs:31-31
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:900-900
  • Core/Resgrid.Services/ChatMessageService.cs:324-324
  • Core/Resgrid.Services/ChatMessageService.cs:330-330
  • Core/Resgrid.Services/ChatMessageService.cs:376-376
  • Core/Resgrid.Services/ChatMessageService.cs:382-382
  • Core/Resgrid.Services/ChatMessageService.cs:195-195
  • Core/Resgrid.Services/ChatMessageService.cs:201-201
  • Core/Resgrid.Services/ChatMessageService.cs:207-207
  • Core/Resgrid.Services/ChatMessageService.cs:353-353
  • Core/Resgrid.Services/ChatMessageService.cs:414-414

Kody rule violation: Use IReadOnlyList for immutable collections

Prompt for LLM

File Core/Resgrid.Model/Repositories/IChatRepositories.cs:

Line 99:

WHAT: The parameter type requires a mutable List<string> in a public interface method. WHY: Forcing callers to supply List<T> is unnecessarily restrictive and inconsistent with sibling methods (e.g., lines 24, 82, 113, 118) that accept IEnumerable<string>; IReadOnlyList<string> or IEnumerable<string> is more appropriate for a read-only input. HOW: Change the parameter type to IReadOnlyList<string> or IEnumerable<string>.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +140 to +148
catch (Exception)
{
// Unique (DepartmentId, DmKey) index backstops the check-then-insert race; adopt the winner.
var winner = await _chatChannelRepository.GetByDmKeyAsync(departmentId, dmKey);
if (winner != null)
return winner;

throw;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: Catch a specific DB exception type (e.g., DbUpdateException) and check for unique-constraint violation instead of catching all Exceptions. WHY: Catching Exception means non-DB errors (NullReferenceException, timeout, etc.) also trigger the re-fetch logic, masking real bugs. HOW: catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex)) { ... } — let all other exceptions propagate immediately.

Also found in:

  • Core/Resgrid.Services/ChatMessageService.cs:294-298
  • Core/Resgrid.Services/ChatChannelService.cs:617-625

Kody rule violation: Implement proper database error checking

Prompt for LLM

File Core/Resgrid.Services/ChatChannelService.cs:

Line 140 to 148:

WHAT: Catch a specific DB exception type (e.g., DbUpdateException) and check for unique-constraint violation instead of catching all Exceptions. WHY: Catching Exception means non-DB errors (NullReferenceException, timeout, etc.) also trigger the re-fetch logic, masking real bugs. HOW: catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex)) { ... } — let all other exceptions propagate immediately.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +451 to +452
var existing = (await _chatChannelRepository.GetByCallIdAsync(callId))?
.FirstOrDefault(c => c.ChannelType == (int)ChatChannelType.Incident);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: Extract the 'GetByCallIdAsync + filter by channel type' query into a shared repository or service method. WHY: The same query pattern appears again at lines 494-495 with only the channel type differing; duplicating it risks divergence. HOW: Create 'GetChannelByCallIdAndTypeAsync(callId, ChatChannelType type)' in the repository and call it from both EnsureIncidentChannelAsync and EnsureCommandChannelAsync.

Also found in:

  • Core/Resgrid.Services/ChatChannelService.cs:494-495

Kody rule violation: Extract common query logic

Prompt for LLM

File Core/Resgrid.Services/ChatChannelService.cs:

Line 451 to 452:

WHAT: Extract the 'GetByCallIdAsync + filter by channel type' query into a shared repository or service method. WHY: The same query pattern appears again at lines 494-495 with only the channel type differing; duplicating it risks divergence. HOW: Create 'GetChannelByCallIdAndTypeAsync(callId, ChatChannelType type)' in the repository and call it from both EnsureIncidentChannelAsync and EnsureCommandChannelAsync.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


var senderDisplayName = await ResolveSenderDisplayNameAsync(request, channel);

var seq = await _chatChannelRepository.AllocateNextMessageSeqAsync(channel.ChatChannelId, DateTime.UtcNow);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

WHAT: AllocateNextMessageSeqAsync returns the newly-allocated sequence but the in-memory channel object is never updated, so the object passed to NotifyMessageSentAsync still carries the pre-send LastMessageSeq. WHY: ChatNotificationService.NotifyMessageSentAsync computes each recipient's unread count as channel.LastMessageSeq - member.LastReadSeq (ChatNotificationService.cs:91,105); with the stale value this undercounts the badge, and the Math.Max(unread,1) mask means recipients always see 1 instead of their true unread total. HOW: After allocating the sequence, assign it back to the local entity before fanning out, e.g. channel.LastMessageSeq = seq; (and optionally channel.LastMessageOn = message.SentOn;) so the notification service computes the correct count.

var seq = await _chatChannelRepository.AllocateNextMessageSeqAsync(channel.ChatChannelId, DateTime.UtcNow);
channel.LastMessageSeq = seq;
channel.LastMessageOn = message?.SentOn ?? DateTime.UtcNow;
Prompt for LLM

File Core/Resgrid.Services/ChatMessageService.cs:

Line 105:

WHAT: AllocateNextMessageSeqAsync returns the newly-allocated sequence but the in-memory `channel` object is never updated, so the object passed to NotifyMessageSentAsync still carries the pre-send LastMessageSeq. WHY: ChatNotificationService.NotifyMessageSentAsync computes each recipient's unread count as `channel.LastMessageSeq - member.LastReadSeq` (ChatNotificationService.cs:91,105); with the stale value this undercounts the badge, and the Math.Max(unread,1) mask means recipients always see 1 instead of their true unread total. HOW: After allocating the sequence, assign it back to the local entity before fanning out, e.g. `channel.LastMessageSeq = seq;` (and optionally `channel.LastMessageOn = message.SentOn;`) so the notification service computes the correct count.

Suggested Code:

var seq = await _chatChannelRepository.AllocateNextMessageSeqAsync(channel.ChatChannelId, DateTime.UtcNow);
channel.LastMessageSeq = seq;
channel.LastMessageOn = message?.SentOn ?? DateTime.UtcNow;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
}

private async Task ProvisionAcksAsync(ChatChannel channel, ChatMessage message, CancellationToken cancellationToken)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance medium

WHAT: ProvisionAcksAsync is awaited on the request thread and inserts one ChatMessageAck row per audience member in a sequential foreach. WHY: For an Urgent message sent to a DepartmentDefault channel, ResolveChannelAudienceUserIdsAsync returns the entire department roster, so a single send triggers O(N) sequential INSERT round-trips against the database on the request path, scaling linearly with department size (thousands of members) and materially slowing the send. HOW: Insert the ack rows in a single batched/bulk operation (or offload provisioning off the request path like the push fan-out), rather than one InsertAsync per user.

// Batch-insert all ack rows in one DB round-trip (or enqueue off the request path)
await _chatMessageAckRepository.InsertBatchAsync(audience
	.Where(u => !string.Equals(u, message.SenderUserId, StringComparison.OrdinalIgnoreCase))
	.Select(u => new ChatMessageAck { ChatMessageAckId = Guid.NewGuid().ToString(), ChatMessageId = message.ChatMessageId, ChatChannelId = message.ChatChannelId, DepartmentId = message.DepartmentId, UserId = u, RequiredOn = message.SentOn })
	.ToList(), cancellationToken);
Prompt for LLM

File Core/Resgrid.Services/ChatMessageService.cs:

Line 513:

WHAT: ProvisionAcksAsync is awaited on the request thread and inserts one ChatMessageAck row per audience member in a sequential foreach. WHY: For an Urgent message sent to a DepartmentDefault channel, ResolveChannelAudienceUserIdsAsync returns the entire department roster, so a single send triggers O(N) sequential INSERT round-trips against the database on the request path, scaling linearly with department size (thousands of members) and materially slowing the send. HOW: Insert the ack rows in a single batched/bulk operation (or offload provisioning off the request path like the push fan-out), rather than one InsertAsync per user.

Suggested Code:

// Batch-insert all ack rows in one DB round-trip (or enqueue off the request path)
await _chatMessageAckRepository.InsertBatchAsync(audience
	.Where(u => !string.Equals(u, message.SenderUserId, StringComparison.OrdinalIgnoreCase))
	.Select(u => new ChatMessageAck { ChatMessageAckId = Guid.NewGuid().ToString(), ChatMessageId = message.ChatMessageId, ChatChannelId = message.ChatChannelId, DepartmentId = message.DepartmentId, UserId = u, RequiredOn = message.SentOn })
	.ToList(), cancellationToken);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

DetailsJson = detailsJson
}, cancellationToken);

await _auditService.SaveAuditLogAsync(new AuditLog

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The AuditLog entry (lines 257–267) omits required security-audit fields: result, trace_id, ip, user_agent, and actor.role. WHY: Tamper-evident audit logs for security-relevant moderation actions must capture full context for SIEM ingestion and forensic analysis. HOW: Extend the AuditLog data payload or model to include result (success/failure), trace_id, ip, user_agent, and the actor's role before saving; ensure the log is stored in WORM/immutable storage and signed.

Kody rule violation: Emit tamper-evident audit logs with required fields

Prompt for LLM

File Core/Resgrid.Services/ChatModerationService.cs:

Line 257:

WHAT: The AuditLog entry (lines 257–267) omits required security-audit fields: result, trace_id, ip, user_agent, and actor.role. WHY: Tamper-evident audit logs for security-relevant moderation actions must capture full context for SIEM ingestion and forensic analysis. HOW: Extend the AuditLog data payload or model to include result (success/failure), trace_id, ip, user_agent, and the actor's role before saving; ensure the log is stored in WORM/immutable storage and signed.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Status = (int)ChatFlagStatus.Open
}, cancellationToken);

PublishModerationEvent(message.DepartmentId, message.ChatChannelId, new { Type = "flagged", flag.ChatMessageFlagId, chatMessageId });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The event type "flagged" is a hardcoded magic string inconsistent with line 269 which uses actionType.ToString(). WHY: Magic strings are error-prone and make it easy to introduce typos that silently break event handling. HOW: Replace with an enum or constant such as ChatModerationActionType.FlagMessage.ToString() or a dedicated ChatEventTypes enum member.

Also found in:

  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:334-334
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:209-209
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:65-65
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatModerationElement.tsx:42-42
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatModerationElement.tsx:41-41
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatModerationElement.tsx:43-43
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatModerationElement.tsx:40-40
  • Core/Resgrid.Services/ChatPermissionService.cs:57-57
  • Core/Resgrid.Services/ChatPermissionService.cs:59-59
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:116-116
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:29-29
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:32-32
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:114-114
  • Core/Resgrid.Services/ChatMessageService.cs:399-399
  • Core/Resgrid.Services/ChatMessageService.cs:369-369
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:46-46
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:65-65
  • Core/Resgrid.Services/ChatPermissionService.cs:125-125
  • Core/Resgrid.Services/ChatPermissionService.cs:64-64
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:47-47
  • Core/Resgrid.Services/ChatPermissionService.cs:118-118
  • Core/Resgrid.Services/ChatPermissionService.cs:120-120
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:101-101
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:115-115
  • Core/Resgrid.Services/ChatPresenceService.cs:29-29
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:66-66
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:49-49
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:87-87
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:119-119
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:139-139
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:146-146
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:149-149
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:116-116
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:130-130
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:167-167
  • Core/Resgrid.Services/ChatPresenceService.cs:36-36
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:127-127
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:133-133
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:143-143
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:124-124
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:136-136
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:181-181
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:152-152
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:156-156
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:226-226
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:191-191
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:252-252
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:236-236
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:267-267
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:261-261

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File Core/Resgrid.Services/ChatModerationService.cs:

Line 71:

WHAT: The event type "flagged" is a hardcoded magic string inconsistent with line 269 which uses actionType.ToString(). WHY: Magic strings are error-prone and make it easy to introduce typos that silently break event handling. HOW: Replace with an enum or constant such as ChatModerationActionType.FlagMessage.ToString() or a dedicated ChatEventTypes enum member.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

if (nodes != null)
{
foreach (var node in nodes)
await chatChannelService.EnsureLaneChannelAsync(node);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: An async network/DB call (EnsureLaneChannelAsync) is invoked once per node inside a foreach loop. WHY: Rule [104] flags N+1-style sequential awaits inside loops because they serialize I/O and scale poorly with node count. HOW: Provide a batch API on IChatChannelService (e.g., EnsureLaneChannelsAsync(IEnumerable nodes)) that provisions all lanes in one round-trip, or use Task.WhenAll(nodes.Select(n => chatChannelService.EnsureLaneChannelAsync(n))) if the calls are independent and the service is concurrency-safe.

Also found in:

  • Core/Resgrid.Services/ChatPermissionService.cs:347-347
  • Core/Resgrid.Services/ChatNotificationService.cs:93-93
  • Core/Resgrid.Services/ChatPermissionService.cs:563-563
  • Core/Resgrid.Services/ChatPermissionService.cs:587-587
  • Core/Resgrid.Services/ChatChannelService.cs:186-186
  • Core/Resgrid.Services/ChatPresenceService.cs:51-55
  • Core/Resgrid.Services/ChatPermissionService.cs:476-476
  • Core/Resgrid.Services/ChatPermissionService.cs:557-557
  • Core/Resgrid.Services/ChatChannelService.cs:221-221
  • Core/Resgrid.Services/ChatChannelService.cs:283-283
  • Core/Resgrid.Services/ChatPermissionService.cs:378-378
  • Core/Resgrid.Services/ChatPermissionService.cs:548-548
  • Core/Resgrid.Services/ChatPermissionService.cs:507-507
  • Core/Resgrid.Services/ChatNotificationService.cs:107-107
  • Core/Resgrid.Services/ChatChannelService.cs:347-347
  • Core/Resgrid.Services/ChatChannelService.cs:292-292
  • Core/Resgrid.Services/ChatPermissionService.cs:497-497
  • Core/Resgrid.Services/ChatPermissionService.cs:537-537
  • Core/Resgrid.Services/ChatChannelService.cs:298-298
  • Core/Resgrid.Services/ChatMessageService.cs:509-509
  • Core/Resgrid.Services/ChatMessageService.cs:519-519
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:103-103
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:194-194
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:193-193

Kody rule violation: Detect N+1 style queries and suggest batching

Prompt for LLM

File Core/Resgrid.Services/ChatProvisioningEventService.cs:

Line 84:

WHAT: An async network/DB call (EnsureLaneChannelAsync) is invoked once per node inside a foreach loop. WHY: Rule [104] flags N+1-style sequential awaits inside loops because they serialize I/O and scale poorly with node count. HOW: Provide a batch API on IChatChannelService (e.g., EnsureLaneChannelsAsync(IEnumerable<Node> nodes)) that provisions all lanes in one round-trip, or use Task.WhenAll(nodes.Select(n => chatChannelService.EnsureLaneChannelAsync(n))) if the calls are independent and the service is concurrency-safe.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

_eventAggregator.AddListener(incidentReopenedHandler);
}

private Action<CallAddedEvent> callAddedHandler = async delegate (CallAddedEvent message)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The field callAddedHandler is assigned only at declaration and never reassigned, but is not marked readonly. WHY: Rule [108] requires immutable instance fields to be marked readonly so the compiler enforces immutability and intent is clear. HOW: Change the declaration to 'private readonly Action callAddedHandler = async delegate (CallAddedEvent message) { ... };'.

Also found in:

  • Core/Resgrid.Services/ChatProvisioningEventService.cs:62-62
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:93-93
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:46-46

Kody rule violation: Use `readonly` or `const` for Immutable Data

Prompt for LLM

File Core/Resgrid.Services/ChatProvisioningEventService.cs:

Line 30:

WHAT: The field callAddedHandler is assigned only at declaration and never reassigned, but is not marked readonly. WHY: Rule [108] requires immutable instance fields to be marked readonly so the compiler enforces immutability and intent is clear. HOW: Change the declaration to 'private readonly Action<CallAddedEvent> callAddedHandler = async delegate (CallAddedEvent message) { ... };'.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

if (message?.Call == null)
return;

var chatChannelService = ServiceLocator.Current.GetInstance<IChatChannelService>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The same ServiceLocator.Current.GetInstance() lookup is repeated in every handler (lines 37, 53, 69, 100). WHY: Rule [14] requires duplicated sequences of statements to be extracted into a named function or utility; repeating the service-resolution boilerplate in four delegates is error-prone and harder to maintain. HOW: Inject IChatChannelService (and IIncidentCommandService) via the constructor as readonly fields, or extract a private helper such as 'IChatChannelService ResolveChat() => ServiceLocator.Current.GetInstance();' and call it from each handler.

Also found in:

  • Web/Resgrid.Web.Eventing/Worker.cs:207-208
  • Core/Resgrid.Services/ChatChannelService.cs:215-221
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1393-1399
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:82-84
  • Core/Resgrid.Services/ChatPermissionService.cs:90-90
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:299-310
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:307-309
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:193-198
  • Core/Resgrid.Model/Chat/ChatModeration.cs:55-55
  • Core/Resgrid.Services/ChatPermissionService.cs:101-101
  • Core/Resgrid.Services/ChatPresenceService.cs:36-36
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:53-53
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:69-69
  • Core/Resgrid.Services/ChatProvisioningEventService.cs:100-100
  • Core/Resgrid.Services/ChatChannelService.cs:341-347
  • Web/Resgrid.Web.Eventing/Worker.cs:220-221
  • Web/Resgrid.Web.Eventing/Worker.cs:214-215
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:48-55
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:473-483
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:57-62
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:227-232
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:347-349
  • Core/Resgrid.Model/Chat/ChatModeration.cs:105-105
  • Core/Resgrid.Model/Chat/ChatModeration.cs:151-151
  • Core/Resgrid.Model/Chat/ChatModeration.cs:203-203
  • Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:261-266
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:380-382
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:430-432
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:488-490
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:519-521
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:555-557
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:590-592
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:994-996
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1051-1053
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1101-1103
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1172-1174
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1200-1202
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:370-377
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1092-1099
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:79-85
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:102-107
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:117-122
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:78-85
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:108-115
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:138-145
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:168-175
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:199-206
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:231-238
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:262-269
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:295-302
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:325-332
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:400-407
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:445-452
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:476-483
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:507-514
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:538-545
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:570-577
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:601-608
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:658-665
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:690-697
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:732-739
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:764-771
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:794-801
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:853-860
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:884-891
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:929-936
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:994-1001
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1047-1054
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1139-1146
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1186-1193
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1235-1242
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1280-1287
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1325-1332
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1356-1363
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1388-1395
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1436-1443
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1493-1500
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1538-1545
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1580-1587
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1610-1617

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Core/Resgrid.Services/ChatProvisioningEventService.cs:

Line 37:

WHAT: The same ServiceLocator.Current.GetInstance<IChatChannelService>() lookup is repeated in every handler (lines 37, 53, 69, 100). WHY: Rule [14] requires duplicated sequences of statements to be extracted into a named function or utility; repeating the service-resolution boilerplate in four delegates is error-prone and harder to maintain. HOW: Inject IChatChannelService (and IIncidentCommandService) via the constructor as readonly fields, or extract a private helper such as 'IChatChannelService ResolveChat() => ServiceLocator.Current.GetInstance<IChatChannelService>();' and call it from each handler.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

break;
case EventingTypes.ChatEvent:
if (ProcessChatEvent != null)
await ProcessChatEvent.Invoke(eventingMessage.DepartmentId, eventingMessage.Payload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The awaited invocation of ProcessChatEvent is not wrapped in a try/catch, so any exception thrown by the handler will propagate unhandled through the RabbitMQ message-processing pipeline. WHY: Rule [1] requires every awaited async operation to be guarded with error handling to prevent unhandled rejections from crashing or destabilizing the message consumer. HOW: Wrap the await in a try/catch block that logs the error with context (department id, event type) and either handles or rethrows the exception appropriately.

Also found in:

  • Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs:603-603
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs:123-123
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatPageElement.tsx:185-185
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:134-134
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:36-36
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/PinsPanel.tsx:48-48
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:50-50
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:257-257
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:315-315
  • Web/Resgrid.Web.Services/Controllers/v4/ChatbotController.cs:318-318
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:150-150
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs:391-391
  • Workers/Resgrid.Workers.Console/Program.cs:462-462
  • Workers/Resgrid.Workers.Console/Program.cs:456-456
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:41-41
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatPanelElement.tsx:136-136
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:72-72
  • Core/Resgrid.Services/ChatNotificationService.cs:46-46
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/MembersPanel.tsx:22-22
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:60-60
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:117-117
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ExportsTab.tsx:97-97
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:146-146
  • Core/Resgrid.Services/PushService.cs:234-234
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:45-45
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/MembersPanel.tsx:36-36
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:33-33
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs:396-396
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs:401-401
  • Core/Resgrid.Services/ChatPresenceService.cs:27-27
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:73-73
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:62-62
  • Core/Resgrid.Services/ChatNotificationService.cs:50-50
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx:33-33
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatbotElement.tsx:53-53
  • Core/Resgrid.Services/ChatNotificationService.cs:93-93
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:83-83
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:154-154
  • Core/Resgrid.Services/ChatNotificationService.cs:56-56
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:197-197
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ThreadPanel.tsx:84-84
  • Core/Resgrid.Services/ChatNotificationService.cs:54-54
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:84-89
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:213-213
  • Core/Resgrid.Services/ChatNotificationService.cs:107-107
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:104-110
  • Core/Resgrid.Services/PushService.cs:239-239
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:57-57
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:199-199
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:48-48
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatHub.ts:244-244
  • Core/Resgrid.Services/ChatPresenceService.cs:29-29
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:133-133
  • Core/Resgrid.Services/ChatPresenceService.cs:41-41
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:119-126
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:200-200
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:61-61
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:201-201
  • Core/Resgrid.Services/ChatPresenceService.cs:36-36
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:109-109
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:124-124
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:84-84
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:106-106
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:121-121
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:79-79
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:102-102
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:117-117

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs:

Line 127:

WHAT: The awaited invocation of ProcessChatEvent is not wrapped in a try/catch, so any exception thrown by the handler will propagate unhandled through the RabbitMQ message-processing pipeline. WHY: Rule [1] requires every awaited async operation to be guarded with error handling to prevent unhandled rejections from crashing or destabilizing the message consumer. HOW: Wrap the await in a try/catch block that logs the error with context (department id, event type) and either handles or rethrows the exception appropriately.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

.WithColumn("ParticipantType").AsInt32().NotNullable().WithDefaultValue(0)
.WithColumn("UserId").AsString(450).Nullable()
.WithColumn("UnitId").AsInt32().Nullable()
.WithColumn("DisplayNameOverride").AsString(int.MaxValue).Nullable()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The DisplayNameOverride column is defined as NVARCHAR(MAX). WHY: Display names are short user-facing labels (typically <128 chars); NVARCHAR(MAX) is wasteful and harms performance. HOW: Use a bounded length such as AsString(128) or AsString(256).

Also found in:

  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:23-23
  • Providers/Resgrid.Providers.Migrations/Migrations/M0105_AddChatMessages.cs:90-90
  • Providers/Resgrid.Providers.Migrations/Migrations/M0105_AddChatMessages.cs:27-27
  • Providers/Resgrid.Providers.Migrations/Migrations/M0105_AddChatMessages.cs:93-93
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:24-24
  • Providers/Resgrid.Providers.Migrations/Migrations/M0105_AddChatMessages.cs:91-91

Kody rule violation: Optimize string column types

Prompt for LLM

File Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:

Line 95:

WHAT: The DisplayNameOverride column is defined as NVARCHAR(MAX). WHY: Display names are short user-facing labels (typically <128 chars); NVARCHAR(MAX) is wasteful and harms performance. HOW: Use a bounded length such as AsString(128) or AsString(256).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// FlagKey index.
Execute.Sql(
"IF NOT EXISTS (SELECT 1 FROM [FeatureFlags] WHERE [FlagKey] = '" + FlagKey + "') " +
"INSERT INTO [FeatureFlags] ([FlagKey], [Name], [Description], [Category], [IsEnabledGlobally]) " +

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

Violates team rule 'Prevent SQL Injection in Queries': Using unsanitized user input in SQL queries can lead to SQL injection attacks. Use parameterized queries to protect against malicious inputs.

Also found in:

  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0108_SeedChatFeatureFlagPg.cs:24-24
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0108_SeedChatFeatureFlagPg.cs:25-25
Prompt for LLM

File Providers/Resgrid.Providers.Migrations/Migrations/M0108_SeedChatFeatureFlag.cs:

Line 24:

Violates team rule 'Prevent SQL Injection in Queries': Using unsanitized user input in SQL queries can lead to SQL injection attacks. Use parameterized queries to protect against malicious inputs.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +12 to +13
// Keep FlagKey in sync with Resgrid.Model.FeatureFlagKeys.ChatSystem.
private const string FlagKey = "Chat.System";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The "Chat.System" flag key is redefined as a local constant here even though Resgrid.Model.FeatureFlagKeys.ChatSystem is the canonical source — the comment itself admits the two must be manually kept in sync. WHY: Rule [6] requires shared string literals (keys, claim types, flag keys) to live in a single place; duplicating them invites drift where the migration seeds one value while runtime code checks another, silently breaking the feature gate. HOW: If migration immutability prevents referencing the model constant directly, keep the hardcoded value but add a unit/integration test (or a build-time check) asserting that the literal seeded here equals FeatureFlagKeys.ChatSystem, so drift is caught automatically instead of by a comment.

Also found in:

  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0108_SeedChatFeatureFlagPg.cs:13-13
  • Web/Resgrid.Web.Services/Startup.cs:760-760
  • Core/Resgrid.Chatbot/Services/ChatbotIngressService.cs:760-760
  • Web/Resgrid.Web.Eventing/Worker.cs:207-207
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1179-1179
  • Web/Resgrid.Web.Eventing/Startup.cs:303-303
  • Core/Resgrid.Services/ChatPermissionService.cs:648-648
  • Core/Resgrid.Services/ChatPermissionService.cs:643-643
  • Web/Resgrid.Web.Eventing/Startup.cs:393-393
  • Core/Resgrid.Model/Chat/ChatModeration.cs:55-55
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:42-42
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:47-47
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts:375-375
  • Core/Resgrid.Services/ChatPresenceService.cs:62-62
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:29-29
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:32-32
  • Web/Resgrid.Web.Eventing/Worker.cs:214-214
  • Web/Resgrid.Web.Eventing/Worker.cs:220-220
  • Core/Resgrid.Model/Chat/ChatModeration.cs:105-105
  • Core/Resgrid.Model/Chat/ChatModeration.cs:151-151
  • Core/Resgrid.Model/Chat/ChatModeration.cs:203-203
  • Core/Resgrid.Services/ChatMessageService.cs:399-399
  • Core/Resgrid.Services/ChatMessageService.cs:369-369
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:46-46
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:65-65
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:42-42
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1207-1207
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:64-64
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:43-43
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:87-87
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:49-49
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:71-71
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:43-43
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:66-66
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:47-47
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:71-71
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:87-87
  • Web/Resgrid.Web.Services/Hubs/ChatHub.cs:64-64

Kody rule violation: Centralize string constants

Prompt for LLM

File Providers/Resgrid.Providers.Migrations/Migrations/M0108_SeedChatFeatureFlag.cs:

Line 12 to 13:

WHAT: The "Chat.System" flag key is redefined as a local constant here even though Resgrid.Model.FeatureFlagKeys.ChatSystem is the canonical source — the comment itself admits the two must be manually kept in sync. WHY: Rule [6] requires shared string literals (keys, claim types, flag keys) to live in a single place; duplicating them invites drift where the migration seeds one value while runtime code checks another, silently breaking the feature gate. HOW: If migration immutability prevents referencing the model constant directly, keep the hardcoded value but add a unit/integration test (or a build-time check) asserting that the literal seeded here equals FeatureFlagKeys.ChatSystem, so drift is caught automatically instead of by a comment.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Execute.Sql("CREATE UNIQUE INDEX IF NOT EXISTS ux_chatchannels_node ON chatchannels (commandstructurenodeid) WHERE commandstructurenodeid IS NOT NULL;");

// At most one chatbot conversation per user per department (ChannelType 8 = Chatbot).
Execute.Sql("CREATE UNIQUE INDEX IF NOT EXISTS ux_chatchannels_department_owner_bot ON chatchannels (departmentid, owneruserid) WHERE channeltype = 8 AND owneruserid IS NOT NULL;");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The magic number 8 is used in raw SQL to filter for Chatbot channel type. WHY: Numeric literals with domain meaning are opaque and error-prone; if the enum value changes or someone misreads the number, bugs are introduced silently. HOW: Define a named constant (e.g., const int ChannelTypeChatbot = 8;) in the migration or a shared constants class and interpolate it into the SQL string.

Also found in:

  • Core/Resgrid.Services/ChatNotificationService.cs:151-151
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ActionsTab.tsx:66-66
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatFormat.ts:152-152
  • Core/Resgrid.Services/ChatModerationService.cs:78-78
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs:65-65
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs:62-62
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChannelList.tsx:52-52
  • Workers/Resgrid.Workers.Console/Program.cs:457-457
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs:123-123
  • Workers/Resgrid.Workers.Console/Program.cs:463-463
  • Core/Resgrid.Services/ChatModerationService.cs:192-192
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs:120-120
  • Workers/Resgrid.Workers.Console/Program.cs:458-458
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts:226-226
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:22-22
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/MessageBubble.tsx:52-52
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:59-59
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:550-550
  • Workers/Resgrid.Workers.Console/Program.cs:464-464
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatActions.ts:40-40
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:127-127
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:273-273
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:65-65
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts:379-379
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:101-101
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts:358-358
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:174-174
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatApi.ts:162-162
  • Providers/Resgrid.Providers.Messaging/GifProvider.cs:125-125
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:62-62
  • Core/Resgrid.Chatbot/Interfaces/IChatCompletionClient.cs:33-33
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:114-114
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:120-120
  • Providers/Resgrid.Providers.Migrations/Migrations/M0104_AddChatChannels.cs:123-123
  • Core/Resgrid.Services/ChatMessageService.cs:437-437
  • Core/Resgrid.Model/Chat/ChatModeration.cs:44-44
  • Core/Resgrid.Services/ChatPresenceService.cs:67-67
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:88-88
  • Core/Resgrid.Chatbot.NLU/Providers/OpenAiCompatibleChatCompletionClient.cs:108-108
  • Core/Resgrid.Model/Chat/ChatChannel.cs:80-80
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:87-87
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1228-1228
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:99-99
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ExportsTab.tsx:91-91
  • Core/Resgrid.Services/ChatMessageService.cs:272-272
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:73-73
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/ExportsTab.tsx:96-96
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:116-116
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/moderation/FlagsTab.tsx:115-115
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:1257-1257
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:70-70
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:107-107
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ChatPanelElement.tsx:54-54
  • Core/Resgrid.Model/Chat/ChatMessage.cs:83-83
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:132-132
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:133-133
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:100-100
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:120-120
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatStore.ts:276-276
  • Core/Resgrid.Model/Chat/ChatChannel.cs:126-126
  • Core/Resgrid.Model/Chat/ChatChannel.cs:202-202
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:152-152
  • Core/Resgrid.Services/ChatMessageService.cs:591-591
  • Core/Resgrid.Model/Chat/ChatMessage.cs:124-124
  • Core/Resgrid.Model/Chat/ChatMessage.cs:179-179
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/chatModerationApi.ts:158-158
  • Core/Resgrid.Services/ChatMessageService.cs:588-588
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/ConversationView.tsx:101-101
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:585-585
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs:619-619
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:162-162
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:163-163
  • Core/Resgrid.Model/Chat/ChatModeration.cs:94-94
  • Core/Resgrid.Model/Chat/ChatModeration.cs:140-140
  • Core/Resgrid.Model/Chat/ChatModeration.cs:192-192
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1213-1213
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1220-1220
  • Web/Resgrid.Web/Areas/User/Apps/src/components/chat/atoms/Composer.tsx:122-122
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1574-1574
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:1575-1575
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:193-193
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:194-194
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:532-532
  • Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:533-533

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File Providers/Resgrid.Providers.MigrationsPg/Migrations/M0104_AddChatChannelsPg.cs:

Line 59:

WHAT: The magic number 8 is used in raw SQL to filter for Chatbot channel type. WHY: Numeric literals with domain meaning are opaque and error-prone; if the enum value changes or someone misreads the number, bugs are introduced silently. HOW: Define a named constant (e.g., `const int ChannelTypeChatbot = 8;`) in the migration or a shared constants class and interpolate it into the SQL string.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

parameters.Add("DepartmentId", departmentId);
parameters.Add("Status", status);
parameters.Add("PageSize", pageSize);
parameters.Add("Offset", (page - 1) * pageSize);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Negative OFFSET causes a SQL error on the default first-page request. The controllers default page = 0 (ChatModerationController.cs:65,281; ChatController.cs:1228) and the services clamp with Math.Max(page, 0) which keeps 0, but the repositories compute Offset = (page - 1) * pageSize, yielding -pageSize. T-SQL 'OFFSET ... ROWS' and Postgres 'OFFSET' both reject negative values, so every default GetFlags/GetActions/Search call throws instead of returning the first page. Use Math.Max(0, page - 1) * pageSize (or have the callers pass a 1-based page) so the first page resolves to offset 0.

parameters.Add("Offset", Math.Max(0, page - 1) * pageSize);
Prompt for LLM

File Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:

Line 1427:

Negative OFFSET causes a SQL error on the default first-page request. The controllers default page = 0 (ChatModerationController.cs:65,281; ChatController.cs:1228) and the services clamp with Math.Max(page, 0) which keeps 0, but the repositories compute Offset = (page - 1) * pageSize, yielding -pageSize. T-SQL 'OFFSET ... ROWS' and Postgres 'OFFSET' both reject negative values, so every default GetFlags/GetActions/Search call throws instead of returning the first page. Use Math.Max(0, page - 1) * pageSize (or have the callers pass a 1-based page) so the first page resolves to offset 0.

Suggested Code:

parameters.Add("Offset", Math.Max(0, page - 1) * pageSize);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

parameters.Add("Ids", ids);
parameters.Add("Query", $"%{escaped}%");
parameters.Add("PageSize", pageSize);
parameters.Add("Offset", (page - 1) * pageSize);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

WHAT: Negative OFFSET in paginated queries causes a SQL error on the default first-page request. WHY: The controllers (ChatController.Search page=0, ChatModerationController.GetFlags page=0, ChatModerationController.GetActions page=0) all default page to 0, but the repository computes Offset = (page - 1) * pageSize, producing -50; both PostgreSQL (OFFSET -50) and SQL Server (OFFSET -50 ROWS FETCH NEXT) reject negative offsets with a runtime SQL error, breaking every default first-page call. HOW: Clamp the page value in the controllers to a minimum of 1 (e.g., page = Math.Max(1, page)) before passing to the service, or clamp in the repository (Offset = Math.Max(0, (page - 1) * pageSize)).

parameters.Add("Offset", Math.Max(0, (page - 1) * pageSize));
Prompt for LLM

File Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:

Line 829:

WHAT: Negative OFFSET in paginated queries causes a SQL error on the default first-page request. WHY: The controllers (ChatController.Search page=0, ChatModerationController.GetFlags page=0, ChatModerationController.GetActions page=0) all default page to 0, but the repository computes Offset = (page - 1) * pageSize, producing -50; both PostgreSQL (OFFSET -50) and SQL Server (OFFSET -50 ROWS FETCH NEXT) reject negative offsets with a runtime SQL error, breaking every default first-page call. HOW: Clamp the page value in the controllers to a minimum of 1 (e.g., page = Math.Max(1, page)) before passing to the service, or clamp in the repository (Offset = Math.Max(0, (page - 1) * pageSize)).

Suggested Code:

parameters.Add("Offset", Math.Max(0, (page - 1) * pageSize));

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// The ChatMessages delete is last; its affected count is the number of messages purged.
var lastAffected = 0;
foreach (var statement in statements)
lastAffected = await connection.ExecuteAsync(statement, parameters, _unitOfWork.Transaction);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: DeleteMessagesByIdsAsync executes 7 DELETE statements in a loop; when _unitOfWork.Connection is null a standalone connection is used with a null transaction, so partial failures leave orphaned child rows. WHY: Multi-step writes must be atomic to avoid inconsistent data. HOW: Wrap the loop in an explicit transaction (BeginTransaction/Commit/Rollback) when _unitOfWork.Transaction is null, ensuring all deletes succeed or all roll back.

Also found in:

  • Core/Resgrid.Services/ChatModerationService.cs:242-242
  • Core/Resgrid.Services/ChatModerationService.cs:93-93
  • Core/Resgrid.Services/ChatModerationService.cs:109-109
  • Core/Resgrid.Services/ChatModerationService.cs:131-131
  • Core/Resgrid.Services/ChatChannelService.cs:335-356
  • Core/Resgrid.Services/ChatModerationService.cs:179-179
  • Core/Resgrid.Services/ChatModerationService.cs:158-158
  • Core/Resgrid.Services/ChatMessageService.cs:146-146
  • Core/Resgrid.Services/ChatModerationService.cs:198-198
  • Core/Resgrid.Services/ChatChannelService.cs:179-192
  • Core/Resgrid.Services/ChatChannelService.cs:207-228
  • Core/Resgrid.Services/ChatMessageService.cs:255-255
  • Core/Resgrid.Services/ChatChannelService.cs:281-308
  • Core/Resgrid.Services/ChatMessageService.cs:230-230
  • Core/Resgrid.Services/ChatMessageService.cs:155-155
  • Core/Resgrid.Services/ChatMessageService.cs:158-158

Kody rule violation: Handle transaction rollbacks properly

Prompt for LLM

File Repositories/Resgrid.Repositories.DataRepository/ChatRepositories.cs:

Line 989:

WHAT: DeleteMessagesByIdsAsync executes 7 DELETE statements in a loop; when _unitOfWork.Connection is null a standalone connection is used with a null transaction, so partial failures leave orphaned child rows. WHY: Multi-step writes must be atomic to avoid inconsistent data. HOW: Wrap the loop in an explicit transaction (BeginTransaction/Commit/Rollback) when _unitOfWork.Transaction is null, ensuring all deletes succeed or all roll back.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public class when_ensuring_chatbot_channels : with_the_chat_channel_service
{
[Test]
public async Task missing_chatbot_channel_should_create_channel_with_owner_and_bot_members()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

WHAT: The method name uses snake_case instead of PascalCase. WHY: .NET naming conventions require PascalCase for methods. HOW: Rename to MissingChatbotChannelShouldCreateChannelWithOwnerAndBotMembers.

Also found in:

  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:272-272
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:104-104
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:173-173
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:86-86
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:203-203
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:304-304
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:129-129
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:253-253
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:235-235
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:336-336
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:200-200
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:222-222
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:301-301
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:289-289
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:324-324
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:83-83
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:321-321
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:269-269
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:150-150
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:18-18
  • Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:126-126

Kody rule violation: Use proper naming conventions

Prompt for LLM

File Tests/Resgrid.Tests/Services/ChatChannelServiceTests.cs:

Line 153:

WHAT: The method name uses snake_case instead of PascalCase. WHY: .NET naming conventions require PascalCase for methods. HOW: Rename to `MissingChatbotChannelShouldCreateChannelWithOwnerAndBotMembers`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

var departmentId = ClaimsAuthorizationHelper.GetDepartmentId();
var userId = ClaimsAuthorizationHelper.GetUserId();

var channel = await _chatChannelService.GetChannelByIdAsync(channelId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: Database query issued before validating that departmentId and userId are valid. WHY: Connect validates departmentId > 0 on line 39, but JoinChannel queries the DB first and only checks department ownership afterward, wasting a query for invalid calls. HOW: Add 'if (departmentId <= 0 || string.IsNullOrWhiteSpace(userId)) return;' before the channel fetch.

Also found in:

  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:79-79
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:102-102
  • Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:117-117

Kody rule violation: Order validations before database queries

Prompt for LLM

File Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:

Line 57:

WHAT: Database query issued before validating that departmentId and userId are valid. WHY: Connect validates departmentId > 0 on line 39, but JoinChannel queries the DB first and only checks department ownership afterward, wasting a query for invalid calls. HOW: Add 'if (departmentId <= 0 || string.IsNullOrWhiteSpace(userId)) return;' before the channel fetch.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

var departmentId = Context.Items.TryGetValue(DepartmentIdContextKey, out var departmentIdValue) && departmentIdValue is int id ? id : 0;

if (!string.IsNullOrWhiteSpace(userId) && RemoveUserConnection(userId, Context.ConnectionId) && departmentId > 0)
await Clients.Group($"chatdept:{departmentId}").SendAsync("chatPresenceChanged", userId, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception risk identified in the awaited Clients.Group(...).SendAsync SignalR call. Network operations can throw transport errors, and an unhandled exception in OnDisconnectedAsync cascades and leaves stale presence state. Wrap the call in a try/catch block with structured logging for operation context.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:

Line 75:

Unhandled exception risk identified in the awaited `Clients.Group(...).SendAsync` SignalR call. Network operations can throw transport errors, and an unhandled exception in `OnDisconnectedAsync` cascades and leaves stale presence state. Wrap the call in a `try/catch` block with structured logging for operation context.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +613 to +626
var valid = await _userManager.VerifyTwoFactorTokenAsync(
user,
_userManager.Options.Tokens.AuthenticatorTokenProvider,
input.TotpCode.Trim());

var result = new ChatActionResult();

if (!valid)
{
result.Success = false;
result.Status = ResponseHelper.Failure;
ResponseHelper.PopulateV4ResponseData(result);
return StatusCode(StatusCodes.Status401Unauthorized, result);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Security high

Brute-force vulnerability identified in the VerifyExportMfa endpoint. The _userManager.VerifyTwoFactorTokenAsync call lacks rate limiting and lockout enforcement, allowing attackers to brute-force the 6-digit TOTP code and bypass MFA protection. Add a per-user rate limit via the existing _cacheProvider.IncrementAsync pattern and invoke _userManager.AccessFailedAsync(user) on invalid attempts.

// Rate-limit TOTP attempts per user to prevent brute-force of the 6-digit code
var attemptKey = $"chat:rl:mfa:{user.Id}";
var attempts = await _cacheProvider.IncrementAsync(attemptKey, TimeSpan.FromMinutes(1));
if (attempts > 5)
{
	await _userManager.AccessFailedAsync(user);
	return StatusCode(StatusCodes.Status429TooManyRequests, new { error = "rate_limited" });
}

var valid = await _userManager.VerifyTwoFactorTokenAsync(
	user,
	_userManager.Options.Tokens.AuthenticatorTokenProvider,
	input.TotpCode.Trim());

var result = new ChatActionResult();

if (!valid)
{
	await _userManager.AccessFailedAsync(user);
	result.Success = false;
	result.Status = ResponseHelper.Failure;
	ResponseHelper.PopulateV4ResponseData(result);
	return StatusCode(StatusCodes.Status401Unauthorized, result);
}
Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 613 to 626:

Brute-force vulnerability identified in the `VerifyExportMfa` endpoint. The `_userManager.VerifyTwoFactorTokenAsync` call lacks rate limiting and lockout enforcement, allowing attackers to brute-force the 6-digit TOTP code and bypass MFA protection. Add a per-user rate limit via the existing `_cacheProvider.IncrementAsync` pattern and invoke `_userManager.AccessFailedAsync(user)` on invalid attempts.

Suggested Code:

// Rate-limit TOTP attempts per user to prevent brute-force of the 6-digit code
var attemptKey = $"chat:rl:mfa:{user.Id}";
var attempts = await _cacheProvider.IncrementAsync(attemptKey, TimeSpan.FromMinutes(1));
if (attempts > 5)
{
	await _userManager.AccessFailedAsync(user);
	return StatusCode(StatusCodes.Status429TooManyRequests, new { error = "rate_limited" });
}

var valid = await _userManager.VerifyTwoFactorTokenAsync(
	user,
	_userManager.Options.Tokens.AuthenticatorTokenProvider,
	input.TotpCode.Trim());

var result = new ChatActionResult();

if (!valid)
{
	await _userManager.AccessFailedAsync(user);
	result.Success = false;
	result.Status = ResponseHelper.Failure;
	ResponseHelper.PopulateV4ResponseData(result);
	return StatusCode(StatusCodes.Status401Unauthorized, result);
}

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


/// <summary>
/// Establishes a recent-MFA step-up proof for chat transcript exports. Verifies the caller's current
/// authenticator (TOTP) code and, on success, records a server-side proof valid for a short window so

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Unsafe type casting identified in the codebase. Direct casts bypass compile-time type safety. Replace direct casts with the as operator or pattern matching, and guard null results before usage.

Kody rule violation: Use safe type casting with as operator

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 583:

Unsafe type casting identified in the codebase. Direct casts bypass compile-time type safety. Replace direct casts with the `as` operator or pattern matching, and guard `null` results before usage.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


// A caller with no 2FA enrolled can never obtain a proof — exports stay blocked until they enroll.
if (!await _userManager.GetTwoFactorEnabledAsync(user))
return StatusCode(StatusCodes.Status403Forbidden, new { error = "mfa_enrollment_required", error_description = "Two-Factor Authentication must be enabled to export chat transcripts." });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Maintenance hazard introduced by the inline magic strings "mfa_enrollment_required" and its description. Duplicating shared string literals invites typos and inconsistency across the codebase. Define constants such as private const string MfaEnrollmentRequiredError = "mfa_enrollment_required"; to centralize these values.

Kody rule violation: Centralize string constants

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 611:

Maintenance hazard introduced by the inline magic strings `"mfa_enrollment_required"` and its description. Duplicating shared string literals invites typos and inconsistency across the codebase. Define constants such as `private const string MfaEnrollmentRequiredError = "mfa_enrollment_required";` to centralize these values.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


// A caller with no 2FA enrolled can never obtain a proof — exports stay blocked until they enroll.
if (!await _userManager.GetTwoFactorEnabledAsync(user))
return StatusCode(StatusCodes.Status403Forbidden, new { error = "mfa_enrollment_required", error_description = "Two-Factor Authentication must be enabled to export chat transcripts." });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Type safety risk identified in the string literal "mfa_enrollment_required". Using raw strings to represent a finite set of export error types invites typos and bypasses compile-time checks. Define an enum, such as ExportErrorCode { MfaEnrollmentRequired, MfaRequired }, to represent these states.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 611:

Type safety risk identified in the string literal `"mfa_enrollment_required"`. Using raw strings to represent a finite set of export error types invites typos and bypasses compile-time checks. Define an enum, such as `ExportErrorCode { MfaEnrollmentRequired, MfaRequired }`, to represent these states.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +646 to +651
var user = await _userManager.GetUserAsync(User);
if (user == null)
return Unauthorized();

if (!await _userManager.GetTwoFactorEnabledAsync(user))
return StatusCode(StatusCodes.Status403Forbidden, new { error = "mfa_enrollment_required", error_description = "Two-Factor Authentication must be enabled to export chat transcripts." });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Code duplication identified in the 2FA enrollment verification sequence. The identical block of retrieving the user, null-checking, and returning a 403 violates DRY principles and increases maintenance burden. Extract a shared helper method such as GetMfaEnrolledUserOrErrorAsync() that returns either the user or an error ActionResult.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 646 to 651:

Code duplication identified in the 2FA enrollment verification sequence. The identical block of retrieving the user, null-checking, and returning a 403 violates DRY principles and increases maintenance burden. Extract a shared helper method such as `GetMfaEnrolledUserOrErrorAsync()` that returns either the user or an error `ActionResult`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +646 to +651
var user = await _userManager.GetUserAsync(User);
if (user == null)
return Unauthorized();

if (!await _userManager.GetTwoFactorEnabledAsync(user))
return StatusCode(StatusCodes.Status403Forbidden, new { error = "mfa_enrollment_required", error_description = "Two-Factor Authentication must be enabled to export chat transcripts." });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Business logic duplication identified in the inline MFA-enrollment enforcement. Implementing domain rules directly in controller methods prevents consistent enforcement and violates separation of concerns. Move the enrollment check into a shared private helper or dedicated MFA service method that both VerifyExportMfa and CheckRecentExportMfaAsync invoke.

Kody rule violation: Extract duplicated business logic

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 646 to 651:

Business logic duplication identified in the inline MFA-enrollment enforcement. Implementing domain rules directly in controller methods prevents consistent enforcement and violates separation of concerns. Move the enrollment check into a shared private helper or dedicated MFA service method that both `VerifyExportMfa` and `CheckRecentExportMfaAsync` invoke.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

/// The caller's current authenticator (TOTP) code, used to establish a recent-MFA step-up proof for PII exports
/// </summary>
[Required]
public string TotpCode { get; set; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Uninitialized state risk identified in the TotpCode auto-property. Omitting default values for class properties violates Rule [31] and introduces null reference risks. Initialize the property to provide deterministic state, such as public string TotpCode { get; set; } = string.Empty;.

Kody rule violation: Initialize properties with default values

Prompt for LLM

File Web/Resgrid.Web.Services/Models/v4/Chat/ChatApiModels.cs:

Line 1427:

Uninitialized state risk identified in the `TotpCode` auto-property. Omitting default values for class properties violates Rule [31] and introduces null reference risks. Initialize the property to provide deterministic state, such as `public string TotpCode { get; set; } = string.Empty;`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
catch (Exception ex)
{
_logger.LogError(ex, $"{Name}: Error processing Personnel Location queue item with an id of {personnelLocationEvent?.EventId}.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Structured logging violation identified in the _logger.LogError call. String interpolation generates a flat message string and prevents identifiers from being captured as queryable fields in the logging backend. Replace the interpolated string with a message template using named placeholders, such as "Error processing Personnel Location queue item with EventId {EventId} for {WorkerName}".

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Workers/Resgrid.Workers.Console/Tasks/PersonnelLocationQueuesProcessorTask.cs:

Line 65:

Structured logging violation identified in the `_logger.LogError` call. String interpolation generates a flat message string and prevents identifiers from being captured as queryable fields in the logging backend. Replace the interpolated string with a message template using named placeholders, such as `"Error processing Personnel Location queue item with EventId {EventId} for {WorkerName}"`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

yield return "# ExportId: " + ExportId;
yield return "# DepartmentId: " + DepartmentId.ToString(CultureInfo.InvariantCulture);
yield return "# RequestedByUserId: " + RequestedByUserId;
yield return "# RequestedOnUtc: " + RequestedOn.ToString("O", CultureInfo.InvariantCulture);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Readability issue caused by utilizing + for string concatenation over interpolation. Manual concatenation is error-prone and decreases code maintainability. Replace the string concatenation with an interpolated string, such as $"# RequestedOnUtc: {RequestedOn.ToString(\"O\", CultureInfo.InvariantCulture)}".

Kody rule violation: Use Template Literals Instead of String Concatenation

Prompt for LLM

File Workers/Resgrid.Workers.Framework/Logic/ChatExportLogic.cs:

Line 289:

Readability issue caused by utilizing `+` for string concatenation over interpolation. Manual concatenation is error-prone and decreases code maintainability. Replace the string concatenation with an interpolated string, such as `$"# RequestedOnUtc: {RequestedOn.ToString(\"O\", CultureInfo.InvariantCulture)}"`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

This comment has been minimized.


// Batch-validate all memberships in a single query instead of one round trip per member.
var membersInDepartment = await _departmentsService.GetMemberUserIdsInDepartmentAsync(departmentId, validatedMemberIds);
if (validatedMemberIds.Any(id => !membersInDepartment.Contains(id)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

O(n*m) complexity in ChatChannelService: membersInDepartment.Contains(id) executes inside an .Any() loop on a List. Convert membersInDepartment to a HashSet before the Any check so each lookup is O(1).

Kody rule violation: Use HashSet for fast membership checks

Prompt for LLM

File Core/Resgrid.Services/ChatChannelService.cs:

Line 230:

O(n*m) complexity in ChatChannelService: membersInDepartment.Contains(id) executes inside an .Any() loop on a List. Convert membersInDepartment to a HashSet<string> before the Any check so each lookup is O(1).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +782 to +790
var members = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(departmentId);
if (members != null)
{
foreach (var member in members)
{
if (member?.UserId != null && candidates.Contains(member.UserId))
result.Add(member.UserId);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Performance regression in GetMemberUserIdsInDepartmentAsync: replacing the cached per-member path (IsUserInDepartmentAsync → GetDepartmentMemberAsync) with GetAllDepartmentMembersUnlimitedAsync loads thousands of DepartmentMember+IdentityUser records via Dapper QueryAsync<DepartmentMember, IdentityUser, DepartmentMember> on every channel-creation call. Replace the full-list load with a targeted query filtering by DepartmentId and candidate UserIds (e.g., SELECT UserId FROM DepartmentMembers WHERE DepartmentId=@deptID AND UserId IN @candidates).

// Use a targeted query that filters at the DB level by candidate UserIds
// rather than loading the entire department member list with User objects.
// e.g. await _departmentMembersRepository.GetMembersByUserIdsAsync(departmentId, candidates);
Prompt for LLM

File Core/Resgrid.Services/DepartmentsService.cs:

Line 782 to 790:

Performance regression in GetMemberUserIdsInDepartmentAsync: replacing the cached per-member path (IsUserInDepartmentAsync → GetDepartmentMemberAsync) with GetAllDepartmentMembersUnlimitedAsync loads thousands of DepartmentMember+IdentityUser records via Dapper QueryAsync<DepartmentMember, IdentityUser, DepartmentMember> on every channel-creation call. Replace the full-list load with a targeted query filtering by DepartmentId and candidate UserIds (e.g., SELECT UserId FROM DepartmentMembers WHERE DepartmentId=@deptId AND UserId IN @candidates).

Suggested Code:

// Use a targeted query that filters at the DB level by candidate UserIds
// rather than loading the entire department member list with User objects.
// e.g. await _departmentMembersRepository.GetMembersByUserIdsAsync(departmentId, candidates);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +773 to +777
var candidates = userIds?
.Where(id => !string.IsNullOrWhiteSpace(id))
.ToHashSet(StringComparer.Ordinal);

var result = new HashSet<string>(StringComparer.Ordinal);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

False rejection in GetMemberUserIdsInDepartmentAsync: candidates.Contains(member.UserId) uses StringComparer.Ordinal while the prior SQL path relied on SQL Server's default CI_AS collation. User IDs arriving with different casing fail to match, causing ChatChannelService.CreateAdHocGroupChannelAsync to throw UnauthorizedAccessException for valid department members. Use StringComparer.OrdinalIgnoreCase for both the candidates and result HashSets to preserve case-insensitive matching.

var candidates = userIds?
    .Where(id => !string.IsNullOrWhiteSpace(id))
    .ToHashSet(StringComparer.OrdinalIgnoreCase);

var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Prompt for LLM

File Core/Resgrid.Services/DepartmentsService.cs:

Line 773 to 777:

False rejection in GetMemberUserIdsInDepartmentAsync: candidates.Contains(member.UserId) uses StringComparer.Ordinal while the prior SQL path relied on SQL Server's default CI_AS collation. User IDs arriving with different casing fail to match, causing ChatChannelService.CreateAdHocGroupChannelAsync to throw UnauthorizedAccessException for valid department members. Use StringComparer.OrdinalIgnoreCase for both the candidates and result HashSets to preserve case-insensitive matching.

Suggested Code:

var candidates = userIds?
    .Where(id => !string.IsNullOrWhiteSpace(id))
    .ToHashSet(StringComparer.OrdinalIgnoreCase);

var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

return result;

// One query for the department's members (no per-user round trips), filtered to the candidates.
var members = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(departmentId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unguarded async operation in DepartmentsService.cs: the awaited call to GetAllDepartmentMembersUnlimitedAsync lacks try/catch, risking unhandled rejection. Wrap the await in a try/catch block and log the error with departmentId and operation context.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Core/Resgrid.Services/DepartmentsService.cs:

Line 782:

Unguarded async operation in DepartmentsService.cs: the awaited call to GetAllDepartmentMembersUnlimitedAsync lacks try/catch, risking unhandled rejection. Wrap the await in a try/catch block and log the error with departmentId and operation context.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// Belt-and-suspenders scrub for key=/api_key= query params. A bounded match timeout caps regex work
// on pathological input (ReDoS guard); on timeout the literal-key replacements have already removed
// the real secrets, so falling through without the query-param scrub is safe.
private static readonly Regex KeyQueryParamRegex = new Regex(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Denial-of-Service vulnerability in GifProvider.cs: KeyQueryParamRegex is instantiated without a match timeout, allowing untrusted input to trigger runaway regex evaluation. Define a timeout when constructing the Regex. Also found at GifProvider.cs:122.

Kody rule violation: Specify Timeout for Regular Expressions

Prompt for LLM

File Providers/Resgrid.Providers.Messaging/GifProvider.cs:

Line 103:

Denial-of-Service vulnerability in GifProvider.cs: KeyQueryParamRegex is instantiated without a match timeout, allowing untrusted input to trigger runaway regex evaluation. Define a timeout when constructing the Regex. Also found at GifProvider.cs:122.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +124 to +127
catch (RegexMatchTimeoutException)
{
// Query-param scrub timed out; literal key replacements above already removed the secrets.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Swallowed exception in GifProvider.cs: the catch (RegexMatchTimeoutException) block contains only a comment with no logging or rethrow, hiding operational signals such as ReDoS attempts. Capture the exception variable and log it with context, e.g. Logging.LogError($"KeyQueryParamRegex timed out after {KeyQueryParamRegex.MatchTimeout}; falling back to literal-key sanitization. Detail: {ex.Message}").

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File Providers/Resgrid.Providers.Messaging/GifProvider.cs:

Line 124 to 127:

Swallowed exception in GifProvider.cs: the catch (RegexMatchTimeoutException) block contains only a comment with no logging or rethrow, hiding operational signals such as ReDoS attempts. Capture the exception variable and log it with context, e.g. Logging.LogError($"KeyQueryParamRegex timed out after {KeyQueryParamRegex.MatchTimeout}; falling back to literal-key sanitization. Detail: {ex.Message}").

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

private static readonly Regex KeyQueryParamRegex = new Regex(
@"\b(api_key|key)=[^&\s""']+",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(250));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic number in GifProvider.cs: the regex match timeout value 250 is an inline literal. Extract it to a named constant such as private static readonly TimeSpan KeyScrubMatchTimeout = TimeSpan.FromMilliseconds(250) for self-documenting, single-source tuning.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File Providers/Resgrid.Providers.Messaging/GifProvider.cs:

Line 106:

Magic number in GifProvider.cs: the regex match timeout value 250 is an inline literal. Extract it to a named constant such as private static readonly TimeSpan KeyScrubMatchTimeout = TimeSpan.FromMilliseconds(250) for self-documenting, single-source tuning.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

{
// Best-effort presence broadcast: the connection is already removed, so a transport
// failure here must not abort the disconnect flow. Log with context and continue.
Resgrid.Framework.Logging.LogException(ex, $"ChatHub presence-offline broadcast failed for user {userId} in department {departmentId}.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unstructured log fields in ChatHub: LogException receives userId and departmentId via string interpolation instead of as separate structured parameters, preventing searchable filtering in SIEM tools. Pass identifiers as structured fields, e.g. Resgrid.Framework.Logging.LogException(ex, "ChatHub presence-offline broadcast failed for user {UserId} in department {DepartmentId}", userId, departmentId).

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:

Line 84:

Unstructured log fields in ChatHub: LogException receives userId and departmentId via string interpolation instead of as separate structured parameters, preventing searchable filtering in SIEM tools. Pass identifiers as structured fields, e.g. Resgrid.Framework.Logging.LogException(ex, "ChatHub presence-offline broadcast failed for user {UserId} in department {DepartmentId}", userId, departmentId).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

{
try
{
await Clients.Group($"chatdept:{departmentId}").SendAsync("chatPresenceChanged", userId, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated inline string literal in ChatHub: the SignalR client-method name "chatPresenceChanged" is hardcoded in SendAsync. Extract it to a constant such as private const string ChatPresenceChangedEvent = "chatPresenceChanged" and reference it in both SendAsync and the client-side handler registration.

Kody rule violation: Centralize string constants

Prompt for LLM

File Web/Resgrid.Web.Eventing/Hubs/ChatHub.cs:

Line 78:

Duplicated inline string literal in ChatHub: the SignalR client-method name "chatPresenceChanged" is hardcoded in SendAsync. Extract it to a constant such as private const string ChatPresenceChangedEvent = "chatPresenceChanged" and reference it in both SendAsync and the client-side handler registration.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

return mfaError;

// Brute-force guard: cap TOTP verification attempts per user per window before we ever verify.
var attempts = await _cacheProvider.IncrementAsync(GetMfaVerifyRateLimitCacheKey(user.Id), MfaVerifyRateLimitWindow);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception in MFA rate-limiting: IncrementAsync against the distributed cache can throw on cache unavailability, surfacing as a raw 500 before the rate-limit decision. Wrap the call in try/catch, log structured context (op:'mfa_rate_limit', userId), and return an application-level result reflecting an explicit fail-open or fail-closed posture. Also found at ChatModerationController.cs:632, ChatModerationController.cs:641, DepartmentsService.cs:782.

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ChatModerationController.cs:

Line 618:

Unhandled exception in MFA rate-limiting: IncrementAsync against the distributed cache can throw on cache unavailability, surfacing as a raw 500 before the rate-limit decision. Wrap the call in try/catch, log structured context (op:'mfa_rate_limit', userId), and return an application-level result reflecting an explicit fail-open or fail-closed posture. Also found at ChatModerationController.cs:632, ChatModerationController.cs:641, DepartmentsService.cs:782.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

Resgrid-Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

Comment on lines +82 to +92
public static void LogError(Exception exception, string extraMessage = "", string correlationId = "",
[CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = 0)
{
Initialize(null);
string msgToLog = BuildExceptionMessage(exception, extraMessage, correlationId, callerFilePath, callerMemberName, callerLineNumber);

if (_logger != null)
_logger.Error(exception, msgToLog);

Console.WriteLine(exception.ToString() + $" {extraMessage}");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicate statement sequence in the LogError method body, nearly identical to LogException with only the log level differing (Error vs Fatal). Rule 14 requires extracting this shared logic into a reusable helper such as LogAtLevel that both methods delegate to.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Core/Resgrid.Framework/Logging.cs:

Line 82 to 92:

Duplicate statement sequence in the LogError method body, nearly identical to LogException with only the log level differing (Error vs Fatal). Rule 14 requires extracting this shared logic into a reusable helper such as LogAtLevel that both methods delegate to.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

if (_logger != null)
_logger.Error(exception, msgToLog);

Console.WriteLine(exception.ToString() + $" {extraMessage}");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

String concatenation with the + operator in Console.WriteLine instead of interpolation. Rule 103 favors a single interpolated expression such as Console.WriteLine($"{exception} {extraMessage}") for readability and fewer errors.

Kody rule violation: Use Template Literals Instead of String Concatenation

Prompt for LLM

File Core/Resgrid.Framework/Logging.cs:

Line 91:

String concatenation with the + operator in Console.WriteLine instead of interpolation. Rule 103 favors a single interpolated expression such as Console.WriteLine($"{exception} {extraMessage}") for readability and fewer errors.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

catch (Exception ex)
{
// Best-effort and explicitly non-fatal to the lane save — log at Error, not Fatal.
Logging.LogError(ex, "Best-effort chat lane channel provisioning failed after lane save.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Missing structured context fields in the error log. Rule [3] requires error logs to include the operation name and relevant identifiers (node.IncidentCommandId, node.DepartmentId, node.CallId) as structured fields rather than a plain message string.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Core/Resgrid.Services/IncidentCommandService.cs:

Line 1487:

Missing structured context fields in the error log. Rule [3] requires error logs to include the operation name and relevant identifiers (node.IncidentCommandId, node.DepartmentId, node.CallId) as structured fields rather than a plain message string.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@ucswift

ucswift commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions 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.

This PR is approved.

@ucswift
ucswift merged commit b4e1eea into master Aug 1, 2026
17 of 19 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.

3 participants