diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Tokens.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Tokens.cs
index 7d379389e..127fe1e0e 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Tokens.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Tokens.cs
@@ -14,12 +14,20 @@ public partial class Flowchart
var flowContext = ctx.TargetContext;
var completedActivity = ctx.ChildContext.Activity;
var flowGraph = flowContext.GetFlowGraph();
+ var tokens = GetTokenList(flowContext);
+
+ // If the completed activity is a terminal node, complete the flowchart immediately.
+ if (completedActivity is ITerminalNode)
+ {
+ tokens.Clear();
+ await flowContext.CompleteActivityAsync();
+ return;
+ }
// Emit tokens for active outcomes.
var outcomes = (ctx.Result as Outcomes ?? Outcomes.Default).Names;
var outboundConnections = flowGraph.GetOutboundConnections(completedActivity);
var activeOutboundConnections = outboundConnections.Where(x => outcomes.Contains(x.Source.Port)).Distinct().ToList();
- var tokens = GetTokenList(flowContext);
foreach (var connection in activeOutboundConnections)
tokens.Add(Token.Create(connection.Source.Activity, connection.Target.Activity, connection.Source.Port));
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/EndTests.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/EndTests.cs
new file mode 100644
index 000000000..933a4b64e
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/EndTests.cs
@@ -0,0 +1,48 @@
+using Elsa.Activities.IntegrationTests.Flow.Workflows;
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Xunit.Abstractions;
+
+namespace Elsa.Activities.IntegrationTests.Flow;
+
+///
+/// Integration tests for the activity.
+///
+public class EndTests(ITestOutputHelper testOutputHelper)
+{
+ private readonly WorkflowTestFixture _fixture = new WorkflowTestFixture(testOutputHelper)
+ .AddWorkflow()
+ .AddWorkflow();
+
+ [Fact(DisplayName = "End terminates sequence execution")]
+ public async Task End_TerminatesSequenceExecution()
+ {
+ // Act
+ var workflowState = await _fixture.RunWorkflowAsync(EndInSequenceWorkflow.DefinitionId);
+
+ // Assert
+ Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
+ var lines = _fixture.CapturingTextWriter.Lines.ToList();
+ Assert.Contains("Before End", lines);
+ Assert.DoesNotContain("After End", lines);
+ }
+
+ [Fact(DisplayName = "End in flowchart terminates flowchart immediately")]
+ public async Task End_InFlowchart_TerminatesFlowchartImmediately()
+ {
+ // Act
+ var workflowState = await _fixture.RunWorkflowAsync(EndInFlowchartWorkflow.DefinitionId);
+
+ // Assert
+ Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
+ var lines = _fixture.CapturingTextWriter.Lines.ToList();
+ Assert.Contains("Start", lines);
+ Assert.Contains("Path A executed", lines);
+ // End is a terminal node - it terminates the flowchart immediately
+ // Path B should not execute because End completes the flowchart
+ Assert.DoesNotContain("Path B executed", lines);
+ // The outer sequence continues after the flowchart completes
+ Assert.Contains("After flowchart", lines);
+ }
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInFlowchartWorkflow.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInFlowchartWorkflow.cs
new file mode 100644
index 000000000..ca657cdd3
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInFlowchartWorkflow.cs
@@ -0,0 +1,50 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+using Elsa.Workflows.Activities.Flowchart.Models;
+
+namespace Elsa.Activities.IntegrationTests.Flow.Workflows;
+
+///
+/// Workflow demonstrating that End terminates a flowchart.
+///
+public class EndInFlowchartWorkflow : WorkflowBase
+{
+ public static readonly string DefinitionId = Guid.NewGuid().ToString();
+
+ protected override void Build(IWorkflowBuilder workflow)
+ {
+ workflow.WithDefinitionId(DefinitionId);
+
+ var start = new WriteLine("Start");
+ var pathA = new WriteLine("Path A executed");
+ var end = new End();
+ var pathB = new WriteLine("Path B executed");
+ var afterFlowchart = new WriteLine("After flowchart");
+
+ workflow.Root = new Sequence
+ {
+ Activities =
+ {
+ new Flowchart
+ {
+ Start = start,
+ Activities =
+ {
+ start,
+ pathA,
+ end,
+ pathB
+ },
+ Connections =
+ {
+ new Connection(start, pathA),
+ new Connection(pathA, end),
+ new Connection(end, pathB)
+ }
+ },
+ afterFlowchart
+ }
+ };
+ }
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInSequenceWorkflow.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInSequenceWorkflow.cs
new file mode 100644
index 000000000..8143bd484
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/Workflows/EndInSequenceWorkflow.cs
@@ -0,0 +1,26 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+
+namespace Elsa.Activities.IntegrationTests.Flow.Workflows;
+
+///
+/// Workflow demonstrating that End activity completes successfully in a sequence.
+///
+public class EndInSequenceWorkflow : WorkflowBase
+{
+ public static readonly string DefinitionId = Guid.NewGuid().ToString();
+
+ protected override void Build(IWorkflowBuilder workflow)
+ {
+ workflow.WithDefinitionId(DefinitionId);
+ workflow.Root = new Sequence
+ {
+ Activities =
+ {
+ new WriteLine("Before End"),
+ new End(),
+ new WriteLine("After End")
+ }
+ };
+ }
+}
diff --git a/test/unit/Elsa.Activities.UnitTests/Flow/EndTests.cs b/test/unit/Elsa.Activities.UnitTests/Flow/EndTests.cs
new file mode 100644
index 000000000..bd2837b00
--- /dev/null
+++ b/test/unit/Elsa.Activities.UnitTests/Flow/EndTests.cs
@@ -0,0 +1,35 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+
+namespace Elsa.Activities.UnitTests.Flow;
+
+///
+/// Unit tests for the activity.
+///
+public class EndTests
+{
+ [Fact(DisplayName = "End implements ITerminalNode interface")]
+ public void End_ImplementsITerminalNode()
+ {
+ // Arrange
+ var endActivity = new End();
+
+ // Assert
+ Assert.IsAssignableFrom(endActivity);
+ }
+
+ [Fact(DisplayName = "End completes execution")]
+ public async Task End_CompletesExecution()
+ {
+ // Arrange
+ var endActivity = new End();
+ var fixture = new ActivityTestFixture(endActivity);
+
+ // Act
+ var context = await fixture.ExecuteAsync();
+
+ // Assert
+ Assert.Equal(ActivityStatus.Completed, context.Status);
+ }
+}