Skip to content

Commit 4f36a93

Browse files
thomhurstclaude
andcommitted
feat: throw PipelineFailedException on pipeline failure for CI exit codes
Add PipelineFailedException that is thrown after the pipeline summary is printed when one or more modules have failed. This ensures CI/CD systems receive a non-zero exit code when the pipeline fails. The exception includes: - The PipelineSummary for inspection - List of failed module names - Clear error message Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 42fe045 commit 4f36a93

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

src/ModularPipelines/Engine/Executors/ExecutionOrchestrator.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22
using Microsoft.Extensions.Logging;
3+
using ModularPipelines.Enums;
34
using ModularPipelines.Exceptions;
45
using ModularPipelines.Helpers;
56
using ModularPipelines.Models;
@@ -147,6 +148,16 @@ private async Task<PipelineSummary> OnEnd(OrganizedModules organizedModules, Sto
147148
_engineCancellationToken.Reason);
148149
}
149150

151+
// Throw exception if pipeline failed - this ensures non-zero exit code in CI
152+
if (pipelineSummary.Status == Status.Failed)
153+
{
154+
var failedModules = pipelineSummary.GetFailedModuleResults()
155+
.Select(r => r.ModuleType.Name)
156+
.ToList();
157+
158+
throw new PipelineFailedException(pipelineSummary, failedModules);
159+
}
160+
150161
return pipelineSummary;
151162
}
152163

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using ModularPipelines.Models;
2+
3+
namespace ModularPipelines.Exceptions;
4+
5+
/// <summary>
6+
/// Thrown when the pipeline completes with one or more failed modules.
7+
/// </summary>
8+
/// <remarks>
9+
/// <para>
10+
/// This exception is thrown after the pipeline summary has been printed to indicate
11+
/// that the pipeline did not complete successfully. This ensures that CI/CD systems
12+
/// receive a non-zero exit code when the pipeline fails.
13+
/// </para>
14+
/// <para><b>When this is thrown:</b></para>
15+
/// <list type="bullet">
16+
/// <item>After the pipeline summary is printed</item>
17+
/// <item>When one or more modules have failed</item>
18+
/// </list>
19+
/// <para><b>Properties available:</b></para>
20+
/// <list type="bullet">
21+
/// <item><see cref="Summary"/> - The pipeline summary containing details of all module results</item>
22+
/// <item><see cref="FailedModules"/> - List of module names that failed</item>
23+
/// </list>
24+
/// </remarks>
25+
/// <seealso cref="PipelineException"/>
26+
public class PipelineFailedException : PipelineException
27+
{
28+
/// <summary>
29+
/// Gets the pipeline summary containing details of all module results.
30+
/// </summary>
31+
public PipelineSummary Summary { get; }
32+
33+
/// <summary>
34+
/// Gets the names of the modules that failed.
35+
/// </summary>
36+
public IReadOnlyList<string> FailedModules { get; }
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="PipelineFailedException"/> class.
40+
/// </summary>
41+
/// <param name="summary">The pipeline summary.</param>
42+
/// <param name="failedModules">The names of modules that failed.</param>
43+
public PipelineFailedException(PipelineSummary summary, IReadOnlyList<string> failedModules)
44+
: base(FormatMessage(failedModules))
45+
{
46+
Summary = summary;
47+
FailedModules = failedModules;
48+
}
49+
50+
private static string FormatMessage(IReadOnlyList<string> failedModules)
51+
{
52+
if (failedModules.Count == 1)
53+
{
54+
return $"Pipeline failed: {failedModules[0]} failed.";
55+
}
56+
57+
return $"Pipeline failed: {failedModules.Count} modules failed ({string.Join(", ", failedModules)}).";
58+
}
59+
}

0 commit comments

Comments
 (0)