{"record":{"id":"5b84ca01af885cb0","repo":"microsoft/autogen","slug":"cannot-convert-control-message-to-type-typeof-tme","errorCode":null,"errorMessage":"Cannot convert control message to type {typeof(TMessage).Name}","messagePattern":"Cannot convert control message to type (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs","lineNumber":557,"sourceCode":"    }\n\n    private async ValueTask DispatchControlMessageAsync<TMessage>(GrpcWorkerConnection<TMessage> connection, ControlMessage controlMsg, CancellationToken cancellationToken)\n    where TMessage : class\n    {\n        if (string.IsNullOrEmpty(controlMsg.Destination))\n        {\n            throw new InvalidOperationException($\"Control message is missing a destination. Message: '{controlMsg}'\");\n        }\n\n        // Ensure the control message is of the correct type\n        if (controlMsg is TMessage typedResponseMessage)\n        {\n            // Send the response back to the client\n            await connection.ResponseStream.WriteAsync(typedResponseMessage, cancellationToken).ConfigureAwait(false);\n        }\n        else\n        {\n            throw new InvalidOperationException($\"Cannot convert control message to type {typeof(TMessage).Name}\");\n        }\n    }\n}\n","sourceCodeStart":539,"sourceCodeEnd":561,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs#L539-L561","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait DispatchControlMessageAsync<Message>(messageWorker, controlMsg, ct);\n\n// after\nif (connection is GrpcWorkerConnection<ControlMessage> controlConn)\n{\n    await DispatchControlMessageAsync(controlConn, controlMsg, ct);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"if (connection is GrpcWorkerConnection<ControlMessage> controlConnection)\n{\n    await DispatchControlMessageAsync(controlConnection, controlMsg, ct);\n}\nelse\n{\n    _logger.LogWarning(\"Control message routed to non-control channel {Type}.\", typeof(TMessage).Name);\n}","tryCatchPattern":"try { await DispatchControlMessageAsync(connection, controlMsg, ct); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Cannot convert control message\"))\n{\n    _logger.LogError(\"Control message sent over {Type} channel; dispatching via control workers instead.\", typeof(TMessage).Name);\n}","preventionTips":["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."],"tags":["dotnet","grpc","generics","control-plane"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}