Skip to content

Troubleshooting

Checklists, diagnostics reference, and FAQ for Pragmatic.Messaging.

  • Class has [MessageHandler] attribute
  • Class implements IMessageHandler<T> (not just the method signature)
  • Class is partial (for [LoggerMessage] integration)
  • Message type T matches the event being published
  • Module is referenced by the host project (SG runs per-project)
  • dotnet clean && dotnet build (stale SG cache)
  • EnableOutbox() called in UseMessaging() config
  • DbContext has [EnableOutbox] attribute
  • SagaEntityTypeConfiguration / OutboxEntityTypeConfiguration applied to DbContext
  • OutboxDeliveryService is running (check logs for Pragmatic.Messaging.OutboxDeliveryService)
  • IMessageTypeRegistry can deserialize the message type (check _Infra.Messaging.TypeRegistry.g.cs)
  • Database has __OutboxMessages table (migration applied)
  • [Retry] attribute is on the handler class (not the method)
  • MaxAttempts > 0 (PRAG0802 if zero)
  • Exception is NOT OperationCanceledException (excluded from retry)
  • Check generated _Pipeline.g.cs for for (var __attempt loop
  • Verify metrics: pragmatic.messaging.retry_attempts counter
  • Check FailureThreshold — lower values trip faster
  • BreakDurationSeconds controls how long the circuit stays open
  • Circuit state is per-handler-type (static) — resets on success
  • After BreakDurationSeconds, next call is “half-open” (if it succeeds, circuit closes)
  • Saga class has [Saga<TState>] attribute
  • Exactly one method has [SagaStart] (PRAG0814 if missing)
  • [InState] values match enum members (check PRAG0810 for inconsistencies)
  • Handler methods return the next action to dispatch (not null)
  • Repository is registered (EnableSagas() or EnableSagaPersistence())
  • Correlation ID is consistent across all events in the workflow
  • Handler calls BatchTracker.ReportSuccessAsync(context, store) after processing
  • Handler calls BatchTracker.ReportFailureAsync(context, store) in catch block
  • EnableBatchProcessing() called in UseMessaging() config
  • BatchDispatcher published items with batch headers (check context.Headers["batch.id"])

IDSeverityMessageFix
PRAG0800Error[MessageHandler] on non-IMessageHandler<T>Add IMessageHandler<T> interface
PRAG0801WarningHandler not partialAdd partial keyword
PRAG0802Error[Retry] with MaxAttempts <= 0Set positive value
PRAG0803Error[MessageMiddleware] on non-IMessageMiddlewareAdd IMessageMiddleware interface
IDSeverityMessageFix
PRAG0810ErrorInconsistent state transitionFix [InState]/NextState graph
PRAG0811InfoState has no handlerExpected for terminal states (Completed, Cancelled)
PRAG0812WarningUnreachable statesAdd transitions from [SagaStart]
PRAG0813Error[Saga<T>] where T is not enumUse enum type parameter
PRAG0814ErrorSaga without [SagaStart]Add [SagaStart] to entry method
IDSeverityMessageFix
PRAG0815WarningHandler consumes cross-boundary without [DependsOn]Add dependency
PRAG0816WarningEvent type has no consumersAdd handler or remove event
PRAG0817ErrorQueue name collisionRename handler
PRAG0818WarningCross-boundary without outboxAdd [EnableOutbox]
IDSeverityMessageFix
PRAG0830Error[EnableOutbox] on non-DbContextApply to DbContext subclass
PRAG0831ErrorMissing Pragmatic.Messaging.EFCore referenceAdd NuGet reference

How do I test handlers without a real transport?

Section titled “How do I test handlers without a real transport?”

Use InMemoryMessageBus (the default). In tests, resolve IMessageBus and publish directly:

var bus = serviceProvider.GetRequiredService<IMessageBus>();
await bus.PublishAsync(new OrderCreated { OrderId = Guid.NewGuid() });

The handler executes synchronously in the same thread.

Can I use multiple transports simultaneously?

Section titled “Can I use multiple transports simultaneously?”

Yes. Use [OnBus("analytics")] on handlers and msg.AddBus("analytics", bus => bus.UseRabbitMq(...)) in config. Handlers without [OnBus] use the default bus.

Inject IDeadLetterStore, call GetAllAsync(), deserialize, and re-publish:

var deadLetters = await deadLetterStore.GetAllAsync();
foreach (var dl in deadLetters)
{
var message = JsonSerializer.Deserialize(dl.Payload, Type.GetType(dl.MessageType)!);
await messageBus.PublishAsync(message!, dl.Context);
}

The handler pipeline is 100% SG-generated (zero reflection). Serialization uses System.Text.Json which requires JsonSerializerContext for full AOT. The IMessageTypeRegistry switch expression is AOT-safe.

Register health checks:

services.AddHealthChecks()
.AddCheck<OutboxHealthCheck>("outbox")
.AddCheck<ChannelTransportHealthCheck>("channels")
.AddCheck<RabbitMqHealthCheck>("rabbitmq");

Use the Pragmatic.Messaging ActivitySource and Meter for OpenTelemetry integration.

Can I use Pragmatic.Messaging without Pragmatic.Composition?

Section titled “Can I use Pragmatic.Messaging without Pragmatic.Composition?”

Yes. Call services.AddPragmaticMessaging(msg => { ... }) directly and add services.AddPragmaticMessageHandlers() (SG-generated) for handler registration.