Try-Catch-Finally in Power Automate with Scopes and Run After
In this series: Power Automate gotchas
Power Automate flows often fail in the worst possible way: a connector times out, one update is skipped, and the owner gets a vague failed run with no business context. You can make flows behave like engineered workflows by using Scope actions as Try, Catch, and Finally blocks. The pattern is simple, but the details matter.
Scopes give Power Automate a real error boundary
A Scope groups actions into a unit with a single status. Create three top-level scopes named Try, Catch, and Finally. Put the actual business process in Try, failure handling in Catch, and cleanup or audit work in Finally.
The key is Configure run after. Catch should run when Try has failed, has timed out, or has been skipped if your design treats skipped as an operational failure. Finally should run after Try succeeds and after Catch completes.
| Scope | Configure run after | Purpose |
|---|---|---|
| Try | Default trigger path | Main business work |
| Catch | Try has failed, is skipped, has timed out | Build error details and notify |
| Finally | Try is successful, Catch is successful, Catch is skipped | Cleanup and audit |
| Terminate | After Catch notification | Set the final run status |
Name actions for humans. A failed action named Update item is not helpful. A failed action named Update invoice status to Posted tells the next engineer what broke before they even open inputs.
Configure run after is the switchboard
Run-after states decide whether downstream actions execute. Open the action menu, choose Configure run after, and select the statuses that should allow the next action to run. For Catch, select failure states from the Try scope. For Finally, usually allow completion from both Try and Catch paths.
| State | Meaning | Typical use |
|---|---|---|
| is successful | Previous action completed | Normal happy path |
| has failed | Previous action returned failure | Catch handling |
| is skipped | Previous action did not run | Optional dependency handling |
| has timed out | Previous action exceeded timeout | Catch handling and alerting |
Be intentional with skipped. A skipped Try might mean a previous condition prevented work, or it might mean a dependency failed. Do not blindly treat skipped as success. In critical flows, include skipped in Catch so the run is investigated.
Capture the failed action with result
The result() function returns an array of action results inside a scope. In Catch, add a Filter array action over the Try scope result and select failed or timed-out actions. Then compose a concise error message with the action name, status, code, and message.
Use this expression as the source for Filter array:
result('Try')
Filter for actions that failed or timed out:
@or(
equals(item()?['status'], 'Failed'),
equals(item()?['status'], 'TimedOut')
)
Compose the first error message:
concat(
first(body('Filter_failed_actions'))?['name'],
' failed with status ',
first(body('Filter_failed_actions'))?['status'],
'. Message: ',
first(body('Filter_failed_actions'))?['error']?['message']
)
Protect against empty arrays. If you include skipped states, there may not be an error object. Use a fallback message when the filtered array is empty. A failure notification should never fail because the failure shape was different.
Terminate should make the run honest
A Catch scope that sends an email can accidentally make the whole flow look successful. If the failure was handled but the business transaction did not complete, add a Terminate action after the notification. Set Status to Failed and include the composed error message.
{
"status": "Failed",
"code": "BUSINESS_PROCESS_FAILED",
"message": "Invoice posting failed. Check the Catch scope output for the failed action and connector message."
}
Use Cancelled only for intentional stops. Failed means the process attempted work and could not complete. Cancelled means the flow intentionally stopped because it should not proceed. That distinction makes monitoring and analytics meaningful.
Retries need idempotent actions
Power Automate actions already have retry policies. Many connector actions retry transient failures automatically. You can adjust retry policy on individual actions, but do it with idempotency in mind. Retrying a read is safe. Retrying a create operation can duplicate records if the target system does not enforce a key.
For operations that may be retried, include a business key. For example, when creating an item in a downstream system, write a correlation ID or source item ID. Before creating, check whether that key already exists. If the previous attempt succeeded but the response failed, the retry should find the existing record and continue.
| Operation | Retry safety | Safer design |
|---|---|---|
| Get item | High | Use default retry |
| Send email | Medium | Include correlation ID in subject or log |
| Create record | Low | Upsert or check by business key |
| Update status | Medium | Make status transitions explicit |
| Call custom API | Depends | Require idempotency key |
Do not hide permanent failures behind aggressive retries. A bad request caused by invalid data will not become valid after four attempts. Use retry for transient platform or network issues, and use validation for bad inputs.
Notifications should be actionable, not noisy
A failure message should tell the owner what to do next. Include the flow name, environment, correlation ID, source record link, failed action, connector message, and run URL. Send it to an operations mailbox or Teams channel, not only to the maker who built the flow.
For high-volume flows, avoid sending one message per failed item unless the business process requires it. Aggregate failures into a log list or Dataverse table, then notify with a summary. The Catch scope should create a trail that can survive email deletion.
Error handling is not complete when the flow catches the exception. It is complete when the next person knows the failed business transaction and the recovery step.
The Try, Catch, Finally pattern makes Power Automate predictable. Group work in scopes, wire run-after states deliberately, extract the failed action with result(), and use Terminate to make the final status honest. That is the difference between a demo flow and an automation your operations team can trust.
Keep reading
Filter Rows: The Lookup GUID Is _parentcustomerid_value
Filter rows is Dataverse OData. Lookups filter on _logicalname_value, not the name. Filter array runs after the download, and @equals does not belong in that box.
Get a Row by ID: Missing Fails the Run, Empty List Rows Does Not
Get a row by ID fails the run when the GUID is missing. List rows with zero matches succeeds. Look up the business key and branch on empty.
When an HTTP Request Is Received: The Callback URL Is a Secret
The HTTP trigger URL carries a SAS signature. Anyone with it can start the flow. Restrict who can call, always Respond, and never treat that URL as public.
Power Automate Do Until: The Loop That Succeeds After Giving Up
A Do until that hits count or timeout still succeeds. Put Get inside the loop, add a Delay, and test the condition after the loop exits.
Power Automate Trigger Conditions: The Run That Never Appears
A trigger condition that is false does not start a run and does not leave run history. How the expression differs from a Condition action, and how to see the skipped checks.
Power Platform September 2026 Update: The App-Builder Skill, Agent Plugins, and the Refreshed Model-Driven UI
What matters in Microsoft's September 2026 Power Platform feature update: the model app-builder skill goes GA, the canvas authoring agent plugin goes GA, generative pages embed in forms and reach connectors, model-driven header refresh is GA with display density in preview, and Power Automate gets server-side search.
Newsletter
New posts, straight to your inbox
One email when a new post goes up. Double opt-in, unsubscribe anytime. Not a daily digest.
Comments
- No comments yet. Be the first.