microsoft/autogen · error · InvalidOperationException

Cannot convert control message to type {typeof(TMessage).Nam

Error message

Cannot convert control message to type {typeof(TMessage).Name}

What it means

InvalidOperationException thrown in GrpcGateway.DispatchControlMessageAsync when the ControlMessage being dispatched is not an instance of the connection's TMessage stream type. The gateway only writes messages whose runtime type matches the connection's generic stream type (Message vs ControlMessage), so a mismatch means the control message arrived on the wrong channel.

Source

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

    }

    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. Dispatch ControlMessage instances only over connections obtained from _controlWorkers (GrpcWorkerConnection<ControlMessage>).
  2. Type-check before dispatch: if (connection is GrpcWorkerConnection<ControlMessage> controlConn) before calling DispatchControlMessageAsync.
  3. Keep worker and gateway on matching AutoGen versions so both agree on which channel carries control traffic.

Example fix

// before
await DispatchControlMessageAsync<Message>(messageWorker, controlMsg, ct);

// after
if (connection is GrpcWorkerConnection<ControlMessage> controlConn)
{
    await DispatchControlMessageAsync(controlConn, controlMsg, ct);
}
Defensive patterns

Strategy: type-guard

Type guard

if (connection is GrpcWorkerConnection<ControlMessage> controlConnection)
{
    await DispatchControlMessageAsync(controlConnection, controlMsg, ct);
}
else
{
    _logger.LogWarning("Control message routed to non-control channel {Type}.", typeof(TMessage).Name);
}

Try / catch

try { await DispatchControlMessageAsync(connection, controlMsg, ct); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Cannot convert control message"))
{
    _logger.LogError("Control message sent over {Type} channel; dispatching via control workers instead.", typeof(TMessage).Name);
}

Prevention

When it happens

Trigger: A ControlMessage routed to a GrpcWorkerConnection<Message> (the data-message channel) or vice versa; generic dispatch code that picked the wrong connection dictionary (_workers vs _controlWorkers); a future message type added to the union without updating dispatch.

Common situations: Gateway refactors that split the single worker stream into Message and ControlMessage channels while older code still dispatches control messages to any connection; helper methods written against the wrong generic parameter; mixed workers on different SDK versions using one channel for both.

Related errors


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