microsoft/autogen · error · InvalidOperationException

Control message is missing a destination. Message: '{control

Error message

Control message is missing a destination. Message: '{controlMsg}'

What it means

InvalidOperationException thrown in GrpcGateway.DispatchControlMessageAsync when a ControlMessage has an empty or null Destination. Control messages are forwarded verbatim to a destination worker over its response stream; with no destination the gateway has no way to route the write, so it refuses the message.

Source

Thrown at dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs:546

    /// <summary>
    /// Writes a response to a worker connection.
    /// </summary>
    /// <param name="connection">The worker connection.</param>
    /// <param name="cloudEvent">The cloud event.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    private async Task WriteResponseAsync(GrpcWorkerConnection<Message> connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default)
    {
        await connection.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false);
    }

    private async ValueTask DispatchControlMessageAsync<TMessage>(GrpcWorkerConnection<TMessage> connection, ControlMessage controlMsg, CancellationToken cancellationToken)
    where TMessage : class
    {
        if (string.IsNullOrEmpty(controlMsg.Destination))
        {
            throw new InvalidOperationException($"Control message is missing a destination. Message: '{controlMsg}'");
        }

        // Ensure the control message is of the correct type
        if (controlMsg is TMessage typedResponseMessage)
        {
            // Send the response back to the client
            await connection.ResponseStream.WriteAsync(typedResponseMessage, cancellationToken).ConfigureAwait(false);
        }
        else
        {
            throw new InvalidOperationException($"Cannot convert control message to type {typeof(TMessage).Name}");
        }
    }
}

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Always populate ControlMessage.Destination with the target worker/agent identity before dispatch.
  2. If the message is meant for the originating worker, set Destination to that worker's client id before sending.
  3. Add a unit test over your control-message builders asserting Destination is non-empty.

Example fix

// before
var msg = new ControlMessage { Command = "reload" };

// after
var msg = new ControlMessage { Destination = clientId, Command = "reload" };
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(controlMsg?.Destination))
{
    throw new ArgumentException($"ControlMessage requires a Destination: {controlMsg}");
}

Type guard

static bool IsRoutable(ControlMessage msg) => !string.IsNullOrEmpty(msg?.Destination);

Try / catch

try { await DispatchControlMessageAsync(connection, controlMsg, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("missing a destination"))
{
    _logger.LogError("Discarding control message without destination: {@Msg}", controlMsg);
}

Prevention

When it happens

Trigger: A worker or gateway component emitting a ControlMessage without setting Destination (for example a keep-alive or ack built by hand); string.Empty slipping through a serializer default; control-plane code paths that broadcast rather than target a single worker.

Common situations: Custom control-protocol extensions that added new message kinds but forgot the destination field; proto3 field-presence pitfalls where an unset string serializes as "" and passes IsNullOrEmpty checks only on the gateway side; version mismatch between gateway and worker control-message schemas.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/8e7c379dd9b11bd0. Report an issue: GitHub.