Skip to content

Fix ChatController naming consistency and streaming cancellation/error handling#1089

Open
Copilot wants to merge 6 commits into
mainfrom
copilot/fix-inconsistent-casing-ai-chat-service
Open

Fix ChatController naming consistency and streaming cancellation/error handling#1089
Copilot wants to merge 6 commits into
mainfrom
copilot/fix-inconsistent-casing-ai-chat-service

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

  • Inspect PR feedback and failing CI for /home/runner/work/EssentialCSharp.Web/EssentialCSharp.Web/EssentialCSharp.Web/Controllers/ChatController.cs
  • Update StreamMessage cancellation/error handling to address the active PR comments and fix the compile error
  • Rebuild the solution and run relevant tests to verify the change
  • Run final validation and summarize results
Original prompt
Please apply the following diffs and create a pull request.
Once the PR is ready, give it a title based on the messages of the fixes being applied.

[{"message":"The field `_AiChatService` uses inconsistent casing ('Ai' instead of 'AI'). It should be renamed to `_AIChatService` to match the type name `AIChatService` and maintain consistency with other fields like `_Logger`.","fixFiles":[{"filePath":"EssentialCSharp.Web/Controllers/ChatController.cs","diff":"diff --git a/EssentialCSharp.Web/Controllers/ChatController.cs b/EssentialCSharp.Web/Controllers/ChatController.cs\n--- a/EssentialCSharp.Web/Controllers/ChatController.cs\n+++ b/EssentialCSharp.Web/Controllers/ChatController.cs\n@@ -15,13 +15,13 @@\n [IgnoreAntiforgeryToken]\n public partial class ChatController : ControllerBase\n {\n-    private readonly AIChatService _AiChatService;\n+    private readonly AIChatService _AIChatService;\n     private readonly ResponseIdValidationService _ResponseIdValidationService;\n     private readonly ILogger<ChatController> _Logger;\n \n     public ChatController(ILogger<ChatController> logger, AIChatService aiChatService, ResponseIdValidationService responseIdValidationService)\n     {\n-        _AiChatService = aiChatService;\n+        _AIChatService = aiChatService;\n         _ResponseIdValidationService = responseIdValidationService;\n         _Logger = logger;\n     }\n@@ -46,7 +41,7 @@\n \n         try\n         {\n-            var (response, responseId) = await _AiChatService.GetChatCompletion(\n+            var (response, responseId) = await _AIChatService.GetChatCompletion(\n                 prompt: request.Message,\n                 previousResponseId: previousResponseId,\n                 enableContextualSearch: request.EnableContextualSearch,\n"}]},{"message":"The method uses `CancellationToken.None` instead of the provided `cancellationToken` parameter when writing error responses. This prevents graceful cancellation of the write operation if the request is aborted. Replace `CancellationToken.None` with `cancellationToken` throughout the StreamMessage method (lines 78, 86, 97, 144, 151, 152, 167, 174, 175) to respect cancellation requests.","fixFiles":[{"filePath":"EssentialCSharp.Web/Controllers/ChatController.cs","diff":"diff --git a/EssentialCSharp.Web/Controllers/ChatController.cs b/EssentialCSharp.Web/Controllers/ChatController.cs\n--- a/EssentialCSharp.Web/Controllers/ChatController.cs\n+++ b/EssentialCSharp.Web/Controllers/ChatController.cs\n@@ -75,7 +75,7 @@\n         if (string.IsNullOrEmpty(request.Message))\n         {\n             Response.StatusCode = 400;\n-            await Response.WriteAsJsonAsync(new { error = \"Message cannot be empty.\" }, CancellationToken.None);\n+            await Response.WriteAsJsonAsync(new { error = \"Message cannot be empty.\" }, cancellationToken);\n             return;\n         }\n \n@@ -83,7 +83,7 @@\n         if (string.IsNullOrEmpty(userId))\n         {\n             Response.StatusCode = 401;\n-            await Response.WriteAsJsonAsync(new { error = \"Unauthorized.\" }, CancellationToken.None);\n+            await Response.WriteAsJsonAsync(new { error = \"Unauthorized.\" }, cancellationToken);\n             return;\n         }\n \n@@ -94,7 +94,7 @@\n         if (!_ResponseIdValidationService.ValidateResponseId(userId, previousResponseId))\n         {\n             Response.StatusCode = 400;\n-            await Response.WriteAsJsonAsync(new { error = \"Invalid conversation context.\" }, CancellationToken.None);\n+            await Response.WriteAsJsonAsync(new { error = \"Invalid conversation context.\" }, cancellationToken);\n             return;\n         }\n \n"}]},{"message":"The when clause checks both `cancellationToken.IsCancellationRequested` and `HttpContext.RequestAborted.IsCancellationRequested`, but this filter will not catch `OperationCanceledException` thrown when either token is cancelled without the corresponding property being set at the exact moment the filter evaluates. Remove the when clause entirely or simplify to catch all `OperationCanceledException` instances, as these are expected during cancellation.","fixFiles":[{"filePath":"EssentialCSharp.Web/Controllers/ChatController.cs","diff":"diff --git a/EssentialCSharp.Web/Controllers/ChatController.cs b/EssentialCSharp.Web/Controllers/ChatController.cs\n--- a/EssentialCSharp.Web/Controllers/ChatController.cs\n+++ b/EssentialCSharp.Web/Controllers/ChatController.cs\n@@ -133,7 +133,7 @@\n             await Response.WriteAsync(\"data: [DONE]\\n\\n\", cancellationToken);\n             await Response.Body.FlushAsync(cancellationToken);\n         }\n-        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested || HttpContext.RequestAborted.IsCancellationRequested)\n+        catch (OperationCanceledException)\n         {\n             LogChatStreamCancelled(_Logger, User.Identity?.Name);\n         }\n"}]},{"message":"The exception handling f...

Agent-Logs-Url: https://github.com/IntelliTect/EssentialCSharp.Web/sessions/c4364690-d679-41d6-a271-69d6c45343af

Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Copilot AI changed the title [WIP] Fix inconsistent casing for _AiChatService field Fix ChatController naming consistency and streaming cancellation/error handling May 13, 2026
Copilot AI requested a review from BenjaminMichaelis May 13, 2026 07:05
@BenjaminMichaelis BenjaminMichaelis marked this pull request as ready for review May 13, 2026 08:39
Copilot AI review requested due to automatic review settings May 13, 2026 08:39
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates ChatController to improve naming consistency and to better align streaming response writes and exception handling with request cancellation behavior for the chat streaming endpoint.

Changes:

  • Renames the AIChatService field from _AiChatService to _AIChatService and updates usages.
  • Updates StreamMessage to use the request cancellationToken (instead of CancellationToken.None) when writing JSON/SSE error responses.
  • Simplifies cancellation and consolidates duplicated ConversationContextLimitExceededException handling based on whether the response has started.
Comments suppressed due to low confidence (1)

EssentialCSharp.Web/Controllers/ChatController.cs:170

  • catch (Exception ex) when (!Response.HasStarted) writes a JSON 500 body using cancellationToken without any protection. If the exception was triggered by a client disconnect / abort (common with streaming endpoints), this write can throw OperationCanceledException (or other I/O exceptions) from inside the catch and bypass your error handling. Consider guarding with if (cancellationToken.IsCancellationRequested) return; or wrapping the write in try/catch for cancellation/I/O.
        }
        catch (Exception ex) when (!Response.HasStarted)
        {
            LogChatStreamErrorBeforeResponseStarted(_Logger, ex, User.Identity?.Name);
            Response.StatusCode = 500;
            Response.ContentType = "application/json";

await Response.Body.FlushAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested || HttpContext.RequestAborted.IsCancellationRequested)
catch (OperationCanceledException)
Comment on lines +142 to +147
{
LogChatStreamErrorMidStream(_Logger, ex, User.Identity?.Name);
try
if (!Response.HasStarted)
{
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"This conversation has grown too long. Please start a new one.\",\"errorCode\":\"context_limit_exceeded\"}\n\n", CancellationToken.None);
await Response.Body.FlushAsync(CancellationToken.None);
Response.StatusCode = 400;
Response.ContentType = "application/json";
await Response.WriteAsJsonAsync(new { error = "This conversation has grown too long. Please start a new one.", errorCode = "context_limit_exceeded" }, cancellationToken);
Agent-Logs-Url: https://github.com/IntelliTect/EssentialCSharp.Web/sessions/ca7b0afb-1731-4a50-ac2e-b4d7152c972a

Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Comment thread EssentialCSharp.Web/Controllers/ChatController.cs Fixed
Agent-Logs-Url: https://github.com/IntelliTect/EssentialCSharp.Web/sessions/ca7b0afb-1731-4a50-ac2e-b4d7152c972a

Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 137 to 140
@@ -137,49 +138,88 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
{
LogChatStreamCancelled(_Logger, User.Identity?.Name);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot If this (#1089 (comment)) is valid feedback, we should address

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in f2ca026. I updated the StreamMessage cancellation filter to also treat OperationCanceledException as expected once the response has already started, which covers disconnect/write-abort cases without swallowing unrelated pre-response cancellations.

Agent-Logs-Url: https://github.com/IntelliTect/EssentialCSharp.Web/sessions/c0627de3-aa52-4d77-a845-1420ecbe2416

Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 14, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.hcaptcha.com
    • Triggering command: /home/REDACTED/work/EssentialCSharp.Web/EssentialCSharp.Web/EssentialCSharp.Web.Tests/bin/Release/net10.0/EssentialCSharp.Web.Tests /home/REDACTED/work/EssentialCSharp.Web/EssentialCSharp.Web/EssentialCSharp.Web.Tests/bin/Release/net10.0/EssentialCSharp.Web.Tests --server dotnettestcli --dotnet-test-pipe /tmp/2ba6abd66c094048b11acc45f6b27272 INVALID,NEW -j DROP (dns block)
  • api.pwnedpasswords.com
    • Triggering command: /home/REDACTED/work/EssentialCSharp.Web/EssentialCSharp.Web/EssentialCSharp.Web.Tests/bin/Release/net10.0/EssentialCSharp.Web.Tests /home/REDACTED/work/EssentialCSharp.Web/EssentialCSharp.Web/EssentialCSharp.Web.Tests/bin/Release/net10.0/EssentialCSharp.Web.Tests --server dotnettestcli --dotnet-test-pipe /tmp/2ba6abd66c094048b11acc45f6b27272 INVALID,NEW -j DROP (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

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