An Azure service that automates the access and use of data across clouds without writing code.
Hi @Abhishake Jaiswal ,
If you're seeing messages land in the dead-letter queue with no corresponding workflow run and no visible failure and it reproduces deterministically on a single message at zero load this is almost always a Split-On debatching issue, not a lock/timeout problem.
The Standard peek-lock Service Bus trigger relies on the workflow run to settle the message (Complete or Abandon). When you enable Split-On (@triggerOutputs()?['body']), the trigger debatches the array and creates one run per item.
If, for a given message, that expression doesn't resolve to a valid, non-empty array i.e. it's an empty array, a single object / non-array, or a shape that doesn't match under the batched (concurrency-on) output then no run is created for that message. And because no run owns the message:
- Nothing calls Complete or Abandon → the peek-lock silently lapses.
-
DeliveryCountincrements on each redelivery. - After
MaxDeliveryCount(default 3), the message is dead-lettered withDeadLetterReason = MaxDeliveryCountExceeded.
No run ever executed, so there's no workflow failure to see. That's why it reproduces every time on one specific message the message's own content is what makes Split-On yield nothing.
Why enabling concurrency triggered it:
Two documented behaviors reinforce this:
- Turning trigger concurrency on reduces the Split-On limit from 100,000 to 100 items, and "if the number of items exceeds this limit, the Split on capability is disabled." (Limits reference)
- The Service Bus trigger is documented to route messages to the DLQ under concurrency + batching edge cases. (Service Bus connector considerations)
So the typical history matches:
- Concurrency off → Split-On limit 100,000, every message got a run (often CPU-saturating), everything settled → no DLQ.
- Concurrency on → Split-On limit dropped to 100 and the batched output shape changed → some messages debatch to nothing → silent DLQ.
- Raising concurrency/waiting-run values only helped the load-induced portion; the deterministic DLQs are debatch failures that tuning can't fix.
Follow below steps to confirm:
- Service Bus Explorer → Peek Mode → the DeadLetter subqueue (non-destructive). On a dead-lettered message, check
DeadLetterReason = MaxDeliveryCountExceededandDeliveryCount = 3, then inspect the message body the failing ones will be an empty / non-array / differently-shaped payload compared to the ones that succeed. - Metrics (filter
EntityName= your subscription, aggregation = Sum): during failures you'll see Incoming rises, Dead-lettered rises, but Completed stays flat messages arrive and dead-letter without ever being completed. Quick count check:
az servicebus topic subscription show \
--resource-group <rg> --namespace-name <ns> \
--topic-name <topic> --name <sub> --query countDetails
Fix:
Turn Split-On off and debatch inside the workflow using a For each over @triggerBody(). A real run is always created, so the message is always settled an empty or odd payload just results in a run that does nothing, instead of silently dead-lettering.
Additional notes:
- If you keep Split-On, make sure no debatched array exceeds 100 items while concurrency is on, otherwise Split-On is disabled for that batch.
- Always use the
?operator (@triggerOutputs()?['body']) so a missing property doesn't hard-fail. - Trigger concurrency is irreversible once enabled plan accordingly.
- If high throughput is the goal, consider a Recurrence trigger + "Get messages" action pattern instead, where you control the pull count and settlement explicitly (Recurrence also avoids waiting-run/lock-expiry issues). Hope this helps!
If the resolution was helpful, kindly take a moment to click on and click on Yes for was this answer helpful. And, if you have any further query do let us know.