Skip to content

Codegen emits an invalid class name for array (batched) message types when duplicate handler TypeNames are disambiguated #3399

Description

@outofrange-consulting

Summary

When the duplicate-TypeName disambiguation in HandlerGraph fires for an array message type (T[]), it builds a generated class name that still contains the [] characters. That is not a valid C# identifier, so the generated handler assembly fails to compile and the whole application dies at runtime with Compilation failures!.

Array message types are not exotic: opts.BatchMessagesOf<T>() makes T[] the handled message type, so any batched message can hit this.

Repro

Full standalone repro (WolverineFx 6.17.2 + WolverineFx.RuntimeCompilation, .NET 10):

using Microsoft.Extensions.Hosting;
using Wolverine;

var builder = Host.CreateApplicationBuilder(args);
builder.UseWolverine(opts =>
{
    opts.MultipleHandlerBehavior = MultipleHandlerBehavior.Separated;
    opts.BatchMessagesOf<ItemDeleted>(batching => batching.BatchSize = 100);
});

var host = builder.Build();
await host.StartAsync();
await host.MessageBus().PublishAsync(new ItemDeleted(1));   // boom
await Task.Delay(2000);
await host.StopAsync();

public record ItemCreated(int Id);
public record ItemDeleted(int Id);

// This class handles TWO message types, one of which is batched (=> ItemDeleted[])
public static class TelemetryHandler
{
    public static void Handle(ItemCreated message) { }
    public static void Handle(ItemDeleted[] messages) { }
}

// A second handler class per message type, so MultipleHandlerBehavior.Separated
// actually splits the chains
public static class AuditHandler
{
    public static void Handle(ItemCreated message) { }
}

public static class PublisherHandler
{
    public static void Handle(ItemDeleted[] messages) { }
}

Result:

System.InvalidOperationException: Compilation failures!
CS1514: { expected
CS1513: } expected
CS1001: Identifier expected
CS1013: Invalid number
CS0116: A namespace cannot directly contain members such as fields, methods or statements
CS1022: Type or namespace definition, or end-of-file expected

dotnet run -- codegen write shows the culprit directly — note the file/class name:

Internal/Generated/WolverineHandlers/ItemCreated1902602641_TelemetryHandlerHandler550305999.cs
Internal/Generated/WolverineHandlers/ItemDeleted[]1177234954_TelemetryHandlerHandler550305999.cs   <-- invalid identifier
Internal/Generated/WolverineHandlers/AuditHandlerHandler1032311623.cs
Internal/Generated/WolverineHandlers/PublisherHandlerHandler1633587262.cs

which produces:

public sealed class ItemDeleted[]1177234954_TelemetryHandlerHandler550305999 : Wolverine.Runtime.Handlers.MessageHandler

Conditions

The chains are only renamed when two of them collide on TypeName, so all of these are needed:

  1. MultipleHandlerBehavior.Separated (one chain per handler class),
  2. a handler class that handles two or more message types, each of which has another handler class (otherwise the chains keep their message-type-based names and never collide),
  3. at least one of those message types is an array — in practice, a type registered with BatchMessagesOf<T>().

Root cause

Both HandlerChain constructors sanitize the brackets:

https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/Runtime/Handlers/HandlerChain.cs#L69

TypeName = messageType.ToSuffixedTypeName(HandlerSuffix).Replace("[]", "Array");

https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/Runtime/Handlers/HandlerChain.cs#L85

TypeName = call.HandlerType.ToSuffixedTypeName(HandlerSuffix).Replace("[]", "Array");

but the duplicate-name disambiguation added for #2004 does not:

https://github.com/JasperFx/wolverine/blob/main/src/Wolverine/Runtime/Handlers/HandlerGraph.cs#L389

chain.TypeName =
    $"{chain.MessageType.ToSuffixedTypeName("")}_{chain.HandlerCalls().First().HandlerType.ToSuffixedTypeName("Handler")}";

chain.MessageType.ToSuffixedTypeName("") yields ItemDeleted[]1177234954 for an array message type, and the brackets flow straight into the generated class name.

Suggested fix

Apply the same sanitization in the disambiguation path:

chain.TypeName =
    $"{chain.MessageType.ToSuffixedTypeName("")}_{chain.HandlerCalls().First().HandlerType.ToSuffixedTypeName("Handler")}"
        .Replace("[]", "Array");

Workaround

Make sure no handler class handles both an array message type and another message type that would separate — e.g. move the T[] handler into its own class, or keep a single handler class for the batched type.

Activity

  1. jeremydmiller commented on Jul 13, 2026

    @jeremydmiller
    Member

    Nuh, uh. We've had tests forever around batch handling like this. What version of everything are you using here? I don't think it's possible for our current tests to be passing and you seeing this.

  2. outofrange-consulting commented on Jul 13, 2026

    @outofrange-consulting
    ContributorAuthor

    Versions are in the description, Wolverine 6.17.2, Multiple message handler behavior to separated. It might have been introduced recently, I remember I already reported something for both single message and batch message of the same type conflicting.
    Anyway the repro sample should help diagnose.

  3. added a commit that references this issue on Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions