microsoft/semantic-kernel · error · InvalidOperationException

An output target has already been set.

Error message

An output target has already been set.

What it means

Thrown by ProcessStepEdgeBuilder.SendEventTo_Internal when assigning a target to an edge that already has one. Each edge is single-target by design; SendEventTo_Internal returns a fresh builder for fluent chaining, and reusing the already-targeted edge instance is treated as a programming error.

Source

Thrown at dotnet/src/Experimental/Process.Core/ProcessStepEdgeBuilder.cs:118

    public ProcessStepEdgeBuilder OnCondition(KernelProcessEdgeCondition condition)
    {
        Verify.NotNull(condition, nameof(condition));
        this.Condition = condition;
        return this;
    }

    /// <summary>
    /// Internally overridable implementation: Signals that the output of the source step should be sent to the specified target when the associated event fires.
    /// </summary>
    /// <param name="target">The output target.</param>
    /// <returns>A fresh builder instance for fluid definition</returns>
    /// <exception cref="InvalidOperationException"></exception>
    /// <exception cref="ArgumentException"></exception>
    internal virtual ProcessStepEdgeBuilder SendEventTo_Internal(ProcessTargetBuilder target)
    {
        if (this.Target is not null)
        {
            throw new InvalidOperationException("An output target has already been set.");
        }

        if (target is ProcessFunctionTargetBuilder functionTargetBuilder)
        {
            if (functionTargetBuilder.Step is ProcessMapBuilder && this.Source is ProcessMapBuilder)
            {
                throw new ArgumentException($"{nameof(ProcessMapBuilder)} may not target another {nameof(ProcessMapBuilder)}.", nameof(target));
            }
        }

        this.Target = target;
        this.Source.LinkTo(this.EventData.EventId, this);

        return new ProcessStepEdgeBuilder(this.Source, this.EventData.EventId, this.EventData.EventName, this.EdgeGroupBuilder, this.Condition);
    }

    /// <summary>
    /// Emit the SK step event as an external event with specific topic name

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Always chain SendEventTo on the builder returned by the previous call rather than the original edge variable.
  2. If you need one event to reach multiple targets, define separate edges (one OnEvent/SendEventTo per target) or use an edge group/fan-out mechanism.
  3. Refactor stored edge references so each is targeted exactly once.

Example fix

// before: same edge targeted twice
var e = step.OnEvent("Done");
e.SendEventTo(a);
e.SendEventTo(b); // throws

// after: chain fresh builders
step.OnEvent("Done").SendEventTo(a);
step.OnEvent("Done").SendEventTo(b);
Defensive patterns

Strategy: validation

Validate before calling

if (edge.Target is not null)
    throw new InvalidOperationException("Edge already has a target; create a new edge for additional targets.");

Type guard

static bool EdgeIsUntargeted(ProcessStepEdgeBuilder e) => e.Target is null;

Try / catch

try { edge.SendEventTo(target); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already been set"))
{ /* use a fresh edge from OnEvent for the new target */ }

Prevention

When it happens

Trigger: Calling SendEventTo twice on the same ProcessStepEdgeBuilder instance instead of chaining on the returned fresh builder; or capturing and reusing an edge reference after it has been assigned a target.

Common situations: Storing the edge from OnEvent(...) in a variable and calling SendEventTo on it in two branches; refactor that inadvertently double-targets; misusing the fluent API by ignoring the returned builder.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/d9ca599800acc76c. Report an issue: GitHub.