{"record":{"id":"099fb59e261f7a87","repo":"elsa-workflows/elsa-core","slug":"invalid-backward-connection-every-path-from-the-source","errorCode":null,"errorMessage":"Invalid backward connection: Every path from the source ('{outboundConnection.Source.Activity.Id}') must go through the target ('{outboundConnection.Target.Activity.Id}') when tracing back to the start.","messagePattern":"Invalid backward connection: Every path from the source \\('(.+?)'\\) must go through the target \\('(.+?)'\\) when tracing back to the start\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Counters.cs","lineNumber":217,"sourceCode":"                hasScheduledActivity |= await MaybeScheduleOutboundActivityAsync(flowGraph, flowScope, flowchartContext, completedActivityContext, outboundConnection, outboundActivity, completionCallback);\n        }\n\n        return hasScheduledActivity;\n    }\n\n    /// <summary>\n    /// Schedules an outbound activity that originates from a backward connection.\n    /// </summary>\n    private static async ValueTask<bool> MaybeScheduleBackwardConnectionActivityAsync(FlowGraph flowGraph, ActivityExecutionContext flowchartContext, ActivityExecutionContext? completedActivityContext, Connection outboundConnection, IActivity outboundActivity, bool connectionFollowed, bool backwardConnectionIsValid, ActivityCompletionCallback completionCallback)\n    {\n        if (!connectionFollowed)\n        {\n            return false;\n        }\n\n        if (!backwardConnectionIsValid)\n        {\n            throw new($\"Invalid backward connection: Every path from the source ('{outboundConnection.Source.Activity.Id}') must go through the target ('{outboundConnection.Target.Activity.Id}') when tracing back to the start.\");\n        }\n\n        var scheduleWorkOptions = new ScheduleWorkOptions\n        {\n            CompletionCallback = completionCallback,\n            Input = new Dictionary<string, object>() { { BackwardConnectionActivityInput, true } },\n            SchedulingActivityExecutionId = completedActivityContext?.Id\n        };\n\n        await flowchartContext.ScheduleActivityAsync(outboundActivity, scheduleWorkOptions);\n        return true;\n    }\n\n    /// <summary>\n    /// Determines the merge mode for a given outbound activity. If the outbound activity is a FlowJoin, it retrieves its configured \n    /// mode. Otherwise, it defaults to FlowJoinMode.WaitAllActive for implicit joins.\n    /// </summary>\n    private static async ValueTask<FlowJoinMode> GetMergeModeAsync(ActivityExecutionContext flowchartContext, IActivity outboundActivity)","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.Counters.cs#L199-L235","documentation":"A backward connection (loop-back edge) is only valid if every path from its source activity back to the flowchart start passes through the connection's target — i.e. the target acts as the loop's re-entry point. When a detected backward edge violates this structural rule, MaybeScheduleBackwardConnectionActivityAsync throws InvalidOperationException because loop counter bookkeeping would be incorrect.","triggerScenarios":"Designer workflows where a loop-back connection targets a node that is not on every path from source to start, e.g. multiple parallel branches merging back at different points, or a backward edge drawn from a mid-branch node to a node outside the loop body.","commonSituations":"Complex loops built by hand in the designer; refactoring a flowchart that removed the intended loop-head node; copies of flows where connection endpoints shifted after node deletion.","solutions":["Rewire the backward connection so its target is the loop head — the node through which every path from source to Start passes.","Restructure the loop so all branches converge at a single join activity before looping back.","Validate the graph before running: trace paths from the connection source to Start and confirm they include the target.","If the edge is not a loop, re-draw it as a forward connection to the correct successor."],"exampleFix":"// before: loop-back drawn from Decision to a mid-body node (not on all paths)\nnew Connection(decision, midBody)\n// after: loop back to the loop-head join that every path from decision passes through\nnew Connection(decision, loopHeadJoin)","handlingStrategy":"validation","validationCode":"foreach (var c in flowchart.Connections.Where(IsBackward))\n{\n    bool valid = AllPathsFromSourceToStartPassThrough(c.Source.Activity, c.Target.Activity, flowchart);\n    if (!valid) throw new InvalidOperationException($\"Backward connection from '{c.Source.Activity.Id}' must target the loop head '{c.Target.Activity.Id}'.\");\n}","typeGuard":null,"tryCatchPattern":"try { await MaybeScheduleBackwardConnectionActivityAsync(...); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Invalid backward connection:\"))\n{\n    logger.LogError(ex, \"Workflow contains an invalid loop-back connection.\");\n}","preventionTips":["Model loops so every path from the loop-back source to Start goes through a single join/loop-head activity.","Validate backward connections after editing flows in the designer.","Prefer dedicated loop constructs (For/While/WhileEach) over hand-drawn backward edges where possible."],"tags":["flowchart","graph-structure","backward-connection","loops"],"backgroundTag":"invalid-state-transition","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}