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
- Dispatch ControlMessage instances only over connections obtained from _controlWorkers (GrpcWorkerConnection<ControlMessage>).
- Type-check before dispatch: if (connection is GrpcWorkerConnection<ControlMessage> controlConn) before calling DispatchControlMessageAsync.
- 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
- Pick connections from _controlWorkers for ControlMessage traffic and _workers for Message traffic.
- Keep gateway and workers on matching AutoGen versions so channel typing agrees.
- Type-check the connection before dispatch rather than relying on the runtime throw.
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
- Control message is missing a destination. Message: '{control
- INVALID_ARGUMENT
- Request message is missing a target. Message: '{request}'.
- Invalid subscription type
- Please set OPENAI_API_KEY environment variable.
AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15).
Data as JSON: /api/errors/5b84ca01af885cb0.
Report an issue: GitHub.