From 52f12457aca70fa8835934b59f72c6b29254bb91 Mon Sep 17 00:00:00 2001 From: David Ortinau Date: Mon, 27 Apr 2026 14:50:09 -0500 Subject: [PATCH] Fix swallowed exceptions in import fire-and-forget pipeline runners Replace empty catch blocks in StartImport, RetryImport (ImportEndpoints) and TriggerCheck (ChannelEndpoints) with proper error handling: - Log the exception via ILoggerFactory (Error level) - Call FailImportAsync to mark the import as Failed with the error message Make VideoImportPipelineService.FailImportAsync public so endpoints can call it directly without needing a separate repository reference. Previously, exceptions thrown before internal pipeline logging (DI resolution failures, scope creation errors, first pipeline step) were silently swallowed, leaving the import permanently stuck in Pending. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/SentenceStudio.Api/ChannelEndpoints.cs | 8 ++++++-- src/SentenceStudio.Api/ImportEndpoints.cs | 15 +++++++++++---- .../Services/VideoImportPipelineService.cs | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/SentenceStudio.Api/ChannelEndpoints.cs b/src/SentenceStudio.Api/ChannelEndpoints.cs index 5e63d30b..463829ad 100644 --- a/src/SentenceStudio.Api/ChannelEndpoints.cs +++ b/src/SentenceStudio.Api/ChannelEndpoints.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using SentenceStudio.Services; using SentenceStudio.Shared.Models; @@ -111,7 +112,8 @@ private static async Task TriggerCheck( string id, ClaimsPrincipal user, [FromServices] ChannelMonitorService channelService, - [FromServices] VideoImportPipelineService pipelineService) + [FromServices] VideoImportPipelineService pipelineService, + [FromServices] ILoggerFactory loggerFactory) { var userProfileId = user.FindFirstValue("user_profile_id"); if (string.IsNullOrEmpty(userProfileId)) @@ -155,7 +157,9 @@ private static async Task TriggerCheck( } catch (Exception ex) { - // Logging happens inside the pipeline service + var logger = loggerFactory.CreateLogger(nameof(ChannelEndpoints)); + logger.LogError(ex, "Unhandled exception in import pipeline for import {ImportId}", import.Id); + await pipelineService.FailImportAsync(import, ex.Message); } }); diff --git a/src/SentenceStudio.Api/ImportEndpoints.cs b/src/SentenceStudio.Api/ImportEndpoints.cs index c7cd546e..fa4281b9 100644 --- a/src/SentenceStudio.Api/ImportEndpoints.cs +++ b/src/SentenceStudio.Api/ImportEndpoints.cs @@ -1,5 +1,6 @@ using System.Security.Claims; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using SentenceStudio.Services; using SentenceStudio.Shared.Models; @@ -55,7 +56,8 @@ private static async Task GetImport( private static async Task StartImport( [FromBody] StartImportRequest request, ClaimsPrincipal user, - [FromServices] VideoImportPipelineService pipelineService) + [FromServices] VideoImportPipelineService pipelineService, + [FromServices] ILoggerFactory loggerFactory) { var userProfileId = user.FindFirstValue("user_profile_id"); if (string.IsNullOrEmpty(userProfileId)) @@ -82,7 +84,9 @@ private static async Task StartImport( } catch (Exception ex) { - // Logging happens inside the pipeline service + var logger = loggerFactory.CreateLogger(nameof(ImportEndpoints)); + logger.LogError(ex, "Unhandled exception in import pipeline for import {ImportId}", import.Id); + await pipelineService.FailImportAsync(import, ex.Message); } }); @@ -97,7 +101,8 @@ private static async Task StartImport( private static async Task RetryImport( string id, ClaimsPrincipal user, - [FromServices] VideoImportPipelineService pipelineService) + [FromServices] VideoImportPipelineService pipelineService, + [FromServices] ILoggerFactory loggerFactory) { var userProfileId = user.FindFirstValue("user_profile_id"); if (string.IsNullOrEmpty(userProfileId)) @@ -132,7 +137,9 @@ private static async Task RetryImport( } catch (Exception ex) { - // Logging happens inside the pipeline service + var logger = loggerFactory.CreateLogger(nameof(ImportEndpoints)); + logger.LogError(ex, "Unhandled exception in import pipeline for import {ImportId}", import.Id); + await pipelineService.FailImportAsync(import, ex.Message); } }); diff --git a/src/SentenceStudio.Shared/Services/VideoImportPipelineService.cs b/src/SentenceStudio.Shared/Services/VideoImportPipelineService.cs index 5096068e..434585b0 100644 --- a/src/SentenceStudio.Shared/Services/VideoImportPipelineService.cs +++ b/src/SentenceStudio.Shared/Services/VideoImportPipelineService.cs @@ -396,7 +396,7 @@ private async Task UpdateStatusAsync(VideoImport import, VideoImportStatus statu _logger.LogDebug("Import {Id} → {Status}", import.Id, status); } - private async Task FailImportAsync(VideoImport import, string errorMessage) + public async Task FailImportAsync(VideoImport import, string errorMessage) { import.Status = VideoImportStatus.Failed; import.ErrorMessage = errorMessage;