6 min readRishi

Power Automate Do Until: The Loop That Succeeds After Giving Up

In this series: Power Automate gotchas

Power Automate Do Until: The Loop That Succeeds After Giving Up

A Dataverse row is supposed to leave Draft. The flow waits in a Do until, the run turns green, and the next action emails "ready." The row is still Draft. The loop did not lie about the condition. It hit count, or it hit timeout, and Power Automate marks that exit as success. Downstream never sees the difference unless you check.

That is the whole trap. Do until is a bounded poll, not a guarantee.

Three ways the loop stops

The action repeats its inner steps until the until-expression is true. Microsoft documents the iteration cap as default 60, maximum 5,000. Expand Change limits and you also get a timeout, shown as an ISO 8601 duration. The designer default is PT1H — one hour. The loop stops at whichever of these happens first:

  1. The condition becomes true.
  2. The iteration count is reached.
  3. The timeout elapses.

A cloud flow itself cannot run longer than 30 days. That is the run-duration limit, so a Do until timeout of P30D is the ceiling, not a way around it. Write days as P30D. PT30D is invalid: T introduces hours, minutes, and seconds (PT1H, PT15M), not calendar days.

There is no pause between iterations unless you put one there. Count 60 with no Delay is sixty Get-row calls as fast as the connector will take them. That burns Power Platform requests and still samples a window of seconds, not the hour you thought you bought.

The Get has to live inside the loop

The until-expression is evaluated against the last outputs of the actions in the loop. A Get you placed before Do until is a snapshot. The loop can run to timeout and the expression will keep reading the same Draft status.

Delay                       PT1M
Get a row                   by id
Until: equals(outputs('Get_a_row')?['body/statuscode'], 1)

On account and contact, statuscode 1 is Active. Other tables use different values, and statecode is a different column (0 Active, 1 Inactive on those same tables). Do not guess from the form label. Run once without the loop, read the Get output, then write equals against the type you actually received — integer for many Dataverse choices, string for a SharePoint Status column.

The ? keeps a missing path from failing the expression. A missing path is empty, equals is false, and you wait until count or timeout instead of seeing a red action. That is the same quiet skip you get from a bad trigger condition, except here a run does exist, and it looks fine.

A Delay of one minute and a count of 60 is an hour of polling, which matches the default timeout. Set both on purpose. If the other system usually finishes in ten minutes, PT15M and a count of 20 with a one-minute Delay is enough. Leaving the defaults because they "look like an hour" is how you get sixty tight retries in the first minute, then a green run.

Success is not "the condition became true"

When count or timeout wins, Do until is still Succeeded. The run continues. Compose, Patch, and Send an email after the loop will fire on a row that never reached the state you were waiting for.

Test the same expression after the loop. If it is still false, Terminate with Status Failed and a message that names the id and the status you saw. That is the difference between a poll and a promise.

Condition: equals(outputs('Get_a_row')?['body/statuscode'], 1)
If no: Terminate  Status = Failed
       Message  = concat('Row ', variables('RowId'), ' still Draft after Do until')

Do not fold this into the until-expression as an or with a timeout flag. The loop already stopped. The Condition after the loop is the audit.

Retries are a different knob. An action retry policy repeats a failed call — 429, 5xx, a dropped connection. It does not poll a field that is still legally Draft. Configure retries on the Get if the connector flakes; do not use retries as a substitute for Do until, and do not use Do until as a substitute for retries.

What Do until is not for

Paging a list. Following @odata.nextLink until it is blank is pagination. Turn on pagination on the list action, or loop on the continuation URL as in large datasets. Do until on "the array is empty" while you keep calling Get items without a skip token will re-read page one.

A human approval. Start and wait for an approval suspends the run until someone responds. Polling an approval record in Do until wastes iterations and still dies at 30 days. If the wait can exceed a run, do not keep the run alive. Write "waiting," end, and let a recurrence or a Dataverse update trigger pick the row up later.

A webhook you already have. If the other system can call back, use that. Business events and a Dataverse "when a row is modified" trigger with a trigger condition on the status you care about beat a parked poll. The poll is for the system that will not call you.

Work that belongs in a child flow. A Do until that also creates related rows, sends mail, and patches three tables is two flows glued together. Run a child flow for the side effects once the status is actually terminal.

Apply to each is the other confusion. That loop walks a collection you already hold. Do until waits for the world to change. Mixing them — Apply to each of a thousand rows, each with a Do until inside — is how a scheduled flow is still running at noon. Filter the list to rows that are already in the state you can process, or fan the wait out to separate runs with a trigger per row.

How to watch a poll in run history

Open a successful run and look at the Do until iteration count in the action. If it is 60 and the inner Get still shows Draft, the condition never became true. If it is 1, the snapshot was already in the target state and you did not need the loop. If it is 3 and the Delay is PT1M, you waited about two minutes after the first check, which is the shape you want.

Each Delay, Get, and Compose inside the loop counts as an action toward the plan's Power Platform request limits. Tight loops without Delay can trip connector throttling before they trip the until-count. A one-minute Delay is not politeness. It is the sample interval.

Do until is the right tool when another system will mutate a row you can re-read, you can live with a bound of minutes or hours, and you are willing to fail the run when the bound expires. Count and timeout are those bounds. They are not the condition. Treat an exit that is not "condition true" as a failure, or the next action will celebrate a wait that gave up.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

  • No comments yet. Be the first.