dapr/dapr · warning
RETRY status returned from app while processing pub/sub even
Error message
RETRY status returned from app while processing pub/sub event %v: %w
What it means
Returned by the gRPC postman when the app answered OnTopicEvent with TopicEventResponse_RETRY. This is the app explicitly asking for redelivery because it cannot process the event right now; the error (wrapping a bare retriable marker) drives the retry policy. It is expected control flow, not a bug — but unbounded repetition means the app never succeeds.
Source
Thrown at pkg/runtime/subscription/postman/grpc/grpc.go:135
//nolint:gosec
if hasErrStatus {
return resiliency.NewCodeError(int32(errStatus.Code()), err)
}
// on error from application, return error for redelivery of event
return err
}
switch res.GetStatus() {
case rtv1.TopicEventResponse_SUCCESS: //nolint:nosnakecase
// on uninitialized status, this is the case it defaults to as an uninitialized status defaults to 0 which is
// success from protobuf definition
diag.DefaultComponentMonitoring.PubsubIngressEvent(ctx, msg.PubSub, strings.ToLower(string(contribpubsub.Success)), "", msg.Topic, elapsed)
return nil
case rtv1.TopicEventResponse_RETRY: //nolint:nosnakecase
diag.DefaultComponentMonitoring.PubsubIngressEvent(ctx, msg.PubSub, strings.ToLower(string(contribpubsub.Retry)), "", msg.Topic, elapsed)
// TODO: add retry error info
return fmt.Errorf("RETRY status returned from app while processing pub/sub event %v: %w", cloudEvent[contribpubsub.IDField], rterrors.NewRetriable(nil))
case rtv1.TopicEventResponse_DROP: //nolint:nosnakecase
log.Warnf("DROP status returned from app while processing pub/sub event %v", cloudEvent[contribpubsub.IDField])
diag.DefaultComponentMonitoring.PubsubIngressEvent(ctx, msg.PubSub, strings.ToLower(string(contribpubsub.Drop)), strings.ToLower(string(contribpubsub.Success)), msg.Topic, elapsed)
return pubsub.ErrMessageDropped
}
// Consider unknown status field as error and retry
diag.DefaultComponentMonitoring.PubsubIngressEvent(ctx, msg.PubSub, strings.ToLower(string(contribpubsub.Retry)), "", msg.Topic, elapsed)
return fmt.Errorf("unknown status returned from app while processing pub/sub event %v, status: %v, err: %w", cloudEvent[contribpubsub.IDField], res.GetStatus(), rterrors.NewRetriable(nil))
}
// DeliverBulk publishes bulk message to a subscriber using gRPC and takes care
// of corresponding responses.
func (g *grpc) DeliverBulk(ctx context.Context, req *postman.DeliverBulkRequest) error {
bscData := *req.BulkSubCallData
psm := req.BulkSubMsgView on GitHub (pinned to 74ad417027)
Solutions
- Make the handler return SUCCESS for processed events and use RETRY only for transient, recoverable failures
- Return DROP (which surfaces as pubsub.ErrMessageDropped) for messages that will never succeed
- Configure retry policy + deadLetterTopic so RETRY loops terminate in a DLQ
- Investigate why the app cannot process the event (its own logs) if RETRY keeps recurring
Example fix
// before
func (s *server) OnTopicEvent(ctx context.Context, e *rtv1.TopicEventRequest) (*rtv1.TopicEventResponse, error) {
return &rtv1.TopicEventResponse{Status: rtv1.TopicEventResponse_RETRY}, nil
}
// after: drop unrecoverable messages
return &rtv1.TopicEventResponse{Status: rtv1.TopicEventResponse_DROP}, nil Defensive patterns
Strategy: retry
Type guard
func isRetryStatusErr(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "RETRY status returned from app while processing pub/sub event ")
} Try / catch
err := deliver(ctx, msg)
if isRetryStatusErr(err) {
// app explicitly asked for redelivery: apply bounded backoff, then dead-letter
if attempts := msgDeliveryCount(msg); attempts >= maxAttempts {
return sendToDeadLetter(ctx, msg)
}
return retryAfter(backoff.For(attempts))
} Prevention
- Return SUCCESS for processed events, DROP for permanently unprocessable ones; reserve RETRY for transient failures
- Set maxDeliveryCount + deadLetterTopic on every subscription that can produce RETRY
- Monitor RETRY rates: sustained RETRY means the consumer is saturated or broken
When it happens
Trigger: Handler returning TopicEventResponse{Status: RETRY} on transient failures (lock contention, downstream 503); every delivery returning RETRY because the message can never be processed (poison message).
Common situations: Apps that map all exceptions to RETRY 'to be safe'; long outages where events keep cycling; no dead-letter topic configured so RETRY loops until max delivery count.
Related errors
- error returned from app while processing pub/sub event %v: %
- error while getting app client: %w
- few message(s) have failed during bulk subscribe operation
- unable to subscribe: %w
- unexpected status code returned from app while processing tr
AI-assisted analysis of dapr/dapr@74ad417027 (2026-08-16).
Data as JSON: /api/errors/a5e189a4cf10d2a6.
Report an issue: GitHub.