Architecture

Power Automate error handling: try-catch scopes, run after and alerts

Make Power Automate cloud flows fail gracefully: retry policies, Try, Catch and Finally scopes, result() to find the failed action, Teams alerts, error logging and a correct final run status.

12 min read
On this page

To make a Power Automate cloud flow fail gracefully, put the work in a Try scope, add a Catch scope whose Run after setting is Has failed and Has timed out, use result('Try') inside Catch to find the action that failed and its error, notify the owner in Teams with a link to the run, and end Catch with a Terminate action set to Failed so the run is still recorded as a failure. Retry policies handle transient errors before your Catch logic ever runs, and a Finally scope that runs on every outcome handles cleanup.

Who this is for and what you will have

This guide is for makers and Power Platform administrators who own business-critical cloud flows: approvals, data sync jobs, SharePoint and Dataverse automation. Today a failure in one of those flows usually means a red run in run history that nobody notices until a user complains. At the end you will have:

  • A reusable Try, Catch and Finally scope pattern.
  • An expression set that extracts the failed action, its status code and its error body.
  • A Teams alert that links straight to the failed run.
  • An optional error log in a Dataverse table.
  • A flow that is still reported as Failed when it fails, so platform alerts and Application Insights keep working.

How failures move through a flow

Every action finishes with a status: Succeeded, Failed, Skipped or TimedOut. By default, an action runs only if the action before it completed with Succeeded. When an action fails, it is marked Failed and every action after it is marked Skipped.

The Run after setting changes that rule. In the designer it is shown as four checkboxes on an action's settings: Is successful, Has failed, Is skipped and Has timed out. In the flow's JSON definition it is the runAfter property, an object that maps each predecessor action to the list of statuses that let the current action run.

A Scope is a control action that groups other actions and gets its own status when they finish. If all actions inside succeed, the scope is Succeeded. If the final action in the scope is Failed or Aborted, the scope is Failed. Because a failed action makes everything after it inside the scope Skipped, a single Run after rule on the scope catches a failure in any of its actions. This is the basis of the try-catch pattern Microsoft recommends in its Power Automate coding guidelines.

Prerequisites

  • A solution-aware cloud flow that you can edit. If you deploy flows between environments, build the pattern before the first deployment; see Power Platform ALM with managed solutions and pipelines.
  • A Microsoft Teams team and channel (or a group chat) for alerts, and the Workflows app allowed in the Teams admin center. The Teams posting actions require it.
  • Optional: a Dataverse table for error logs, if the environment has Dataverse.
  • Optional: an Application Insights resource and a managed environment, if you want platform-level telemetry and alerting for flow runs.

Step 1: Let retry policies handle transient failures first

Try-catch is for errors that won't fix themselves. Throttling, timeouts and brief service outages are better handled by the action's retry policy, which resends the request before the action is ever marked Failed.

Microsoft documents the default retry policy by performance profile:

Performance profileDefault retry behaviour
LowUp to two retries at exponentially increasing intervals, scaling by 5 minutes up to roughly 10 minutes for the last retry
Medium, HighUp to 12 retries at exponentially increasing intervals, scaling by 7 seconds up to roughly 1 hour for the last retry

A flow uses its owner's plan to determine the profile. You can change the policy on any action that supports it: open the action's settings and set Retry policy to exponential interval, fixed interval or none. Microsoft's guidance prefers exponential intervals because they give a struggling service more time to recover. The documented limits are 90 retry attempts, a maximum delay of one day and a minimum delay of five seconds.

Set the policy to none on actions that must not be repeated, for example an HTTP call to an API that isn't idempotent, and handle the failure in Catch instead.

Step 2: Build the Try, Catch and Finally scopes

  1. Open the flow in the designer. Below the trigger, select +, search for scope, and under Control select Scope. Rename it Try.
  2. Drag or add the flow's business actions inside Try. Keep the trigger and any Response action outside scopes; Microsoft lists this as a known limitation for clarity and correct behaviour.
  3. Add a second Scope directly after Try and rename it Catch.
  4. Open Catch, go to its settings, and under Run after for Try select Has failed and Has timed out, then clear Is successful. Select the new statuses before clearing the default, because at least one status must always be selected.
  5. Add a third Scope after Catch and rename it Finally. Set its Run after for Catch to all four statuses: Is successful, Has failed, Is skipped and Has timed out. When Try succeeds, Catch is skipped, and Finally still runs.

In code view, the two run-after rules look like this:

"Catch": {
  "type": "Scope",
  "actions": {},
  "runAfter": {
    "Try": ["Failed", "TimedOut"]
  }
},
"Finally": {
  "type": "Scope",
  "actions": {},
  "runAfter": {
    "Catch": ["Succeeded", "Failed", "Skipped", "TimedOut"]
  }
}

Rename scopes before you reference them in expressions. Expressions refer to an action by name, so result('Try') breaks if you later rename the scope.

Step 3: Find the action that failed

The result() function takes the name of a scope, For_each or Until action and returns an array with the results of its top-level actions. Each item has the same attributes as the actions() function: name, start and end time, status, code, inputs, outputs and tracking IDs.

  1. As the first action inside Catch, add Filter array.
  2. Set From to the expression result('Try').
  3. Set the condition to the expression item()?['status'] is equal to Failed. In advanced mode the condition is:
@equals(item()?['status'], 'Failed')
  1. Add Compose actions (or one Compose with a small JSON object) that pick out the first failed action's details:
ValueExpression
Failed action namefirst(body('Filter_array'))?['name']
Error codefirst(body('Filter_array'))?['code']
HTTP status codefirst(body('Filter_array'))?['outputs']?['statusCode']
Error body as textstring(first(body('Filter_array'))?['outputs']?['body'])

The rest of this guide refers to these values as Error_details.

The outputs.body value is whatever the failed action returned, so its shape depends on the connector. Store it as a string rather than trying to parse a specific error schema for every connector.

Two limits matter here. result() returns only first-level actions in the scope, not actions nested inside a Condition or Switch; if Try contains a condition, the failed item you see is the condition itself. And a flow supports a maximum of eight nested levels of scopes, conditions, switch cases and apply-to-each loops, so don't wrap every block in its own try-catch.

The workflow() function returns details about the current flow and run: name, type, id, location, run and tags. In Power Automate, tags includes flowDisplayName and environmentName, and workflow().run.name returns the current run's name. Add a Compose action named Run_link with this input:

https://make.powerautomate.com/environments/@{workflow()?['tags']?['environmentName']}/flows/@{workflow()?['tags']?['logicAppName']}/runs/@{workflow()?['run']?['name']}

Microsoft's error-handling guidance builds the same URL from environmentName and logicAppName in the tags object. Use @{workflow()?['tags']?['flowDisplayName']} for a readable flow name in the alert.

Step 5: Notify the owner in Teams

  1. Inside Catch, add the Microsoft Teams action Post message in a chat or channel.
  2. Set Post as and Post in to fit your setup, for example the Flow bot posting to a support channel.
  3. Write a short message that includes the flow display name, the failed action from Error_details, the run link, and the time from utcNow().

Keep alerts short and actionable. The Teams connector documents a message size limit of approximately 28 KB including all HTML; larger messages fail with "Request Entity too large", so don't paste whole error bodies into the message. Posting to private channels isn't supported, a single message can @mention up to 20 users and 20 tags, and Flow bot operations share a limit of 25 non-GET requests per connection per 300 seconds. If a loop can fail many times in one run, send one summary message per run rather than one per failed item.

Microsoft also cautions against overdoing custom logging and alerting: every extra action adds load, and frequent alerts are easy to ignore. Alert on failures that someone has to act on.

Step 6: Log the error to Dataverse

A Teams message disappears from view quickly. For flows that matter, also keep a record.

  1. Create a Dataverse table, for example Flow Error, with text columns for flow name, failed action, error details and run link, and a date and time column for when it happened.
  2. Inside Catch, after the Teams action, add the Dataverse action Add a new row, select the Flow Error table, and map the columns to workflow()?['tags']?['flowDisplayName'], the outputs of Error_details and Run_link, and utcNow().

Because the table is a normal Dataverse table, you can build a model-driven view or a Power BI report on it, share it with support staff, and clean it up with a bulk delete job. If the logging action itself can fail, don't let that hide the original error: set the Terminate action in the next step to run after the logging action on every status.

Step 7: Keep the run marked as Failed

If Catch finishes successfully, the run's final status is evaluated from the actions that ran last, and a failure that was "handled" can show as Succeeded in run history. That hides the problem from anyone scanning run history and from monitoring that counts failed runs.

Add Terminate as the last action inside Catch:

  • Status: Failed
  • Code: a short code such as TryScopeFailed
  • Message: the failed action name from Error_details

Terminate stops the run, cancels actions in progress and skips the remaining actions, so place it after the alert and the log entry. That also means a Finally scope is skipped once Terminate runs inside Catch. If you need cleanup in Finally on every outcome, move Terminate out of Catch: add a Condition after Finally that checks whether actions('Try')?['status'] is not equal to Succeeded, and put Terminate in its True branch. Terminate can't be placed inside Apply to each or Do until loops.

In code view:

"Terminate": {
  "type": "Terminate",
  "inputs": {
    "runStatus": "Failed",
    "runError": {
      "code": "TryScopeFailed",
      "message": "@{first(body('Filter_array'))?['name']} failed"
    }
  },
  "runAfter": {
    "Add_a_new_row": ["Succeeded", "Failed", "Skipped", "TimedOut"]
  }
}

Step 8: Add platform-level monitoring

In-flow alerts only fire when the flow gets far enough to run Catch. Two platform features cover what they can't see.

  • Owner email alerts. Microsoft documents that the Power Automate service emails flow owners for common or critical failures such as broken connections or throttling, with error details and troubleshooting tips.
  • Application Insights. In managed environments you can export cloud flow telemetry to Application Insights. Flow runs land in the requests table and triggers and actions in the dependencies table. A Failed requests alert on the resource fires on failed runs, and a custom log search alert can target one flow:
let myEnvironmentId = '<environment id>';
let myFlowId = '<flow id>';
requests
| where timestamp > ago(1d)
| where customDimensions['resourceProvider'] == 'Cloud Flow'
| where customDimensions['signalCategory'] == 'Cloud flow runs'
| where customDimensions['environmentId'] == myEnvironmentId
| where customDimensions['resourceId'] == myFlowId
| where success == false

Microsoft notes this telemetry isn't transactional and small gaps can occur; run history in Power Automate remains the complete record. This is another reason Step 7 matters: a handled failure that ends as Succeeded never shows up as a failed request.

Verification

  1. Temporarily force a failure inside Try, for example a Compose with the expression int('not-a-number'), or an action pointed at a list that doesn't exist.
  2. Run the flow. In run history, confirm Try is Failed, Catch ran, Finally ran, and the run is Failed with your Terminate code and message.
  3. Check that the Teams message arrived and its link opens the same run.
  4. Check that a Flow Error row was created.
  5. Remove the forced failure, run again, and confirm Catch is Skipped, Finally ran, and the run is Succeeded.

Troubleshooting

Catch never runs. Its Run after setting still has only Is successful, or it points at the wrong predecessor. It must run after Try with Has failed (and usually Has timed out).

result('Try') returns an empty array or the wrong action. The name doesn't match the scope's internal name, or the failed action is nested in a condition or switch. result() returns only first-level actions.

"Request Entity too large" from Teams. The message exceeded about 28 KB. Send the action name, status and run link, and keep full error bodies in the log table.

"BotNotInConversationRoster" in GCC, GCC High or DoD. The Flow bot poster isn't supported in government cloud tenants. Post as User instead.

Flow won't save after adding scopes. You exceeded eight nested levels. Flatten the structure or move a block into a child flow.

Run shows Succeeded after a handled failure. Add the Terminate action with status Failed at the end of Catch, as in Step 7.

Failures still slip through. Some failures happen before Try starts, such as trigger or connection problems. Rely on owner email alerts and Application Insights for those.

Closing checklist

  • Retry policy reviewed on every action that calls an external service; none on non-idempotent calls.
  • Business actions inside a Try scope; trigger and Response outside.
  • Catch runs after Try on Has failed and Has timed out.
  • Finally runs after Catch on all four statuses.
  • result('Try') filtered to failed items, with a compact error object.
  • Teams alert with flow name, failed action and run link, kept well under 28 KB.
  • Error row written to Dataverse for flows that need an audit trail.
  • Terminate with Failed so the run stays a failure.
  • Owner email alerts and, in managed environments, Application Insights alerts on failed runs.

References

Questions people ask

How do I do try-catch in Power Automate?

Put the main actions in a Scope named Try, add a second Scope named Catch after it, and set Catch's Run after setting to Has failed and Has timed out for Try. Catch then runs only when something inside Try fails. A third Finally scope that runs after Catch on every status handles cleanup.

How do I get the error message of the failed action in a scope?

Use the result() expression with the scope's name, for example result('Try'), and filter the array to items whose status equals Failed. Each item carries the action name, status, code, inputs and outputs. The function returns only top-level actions, not actions nested in conditions or switches.

Why does my flow show Succeeded even though an action failed?

When the Catch scope handles the error and finishes successfully, the run is evaluated on the actions that ran last, so it can be reported as succeeded. Add a Terminate action with status Failed at the end of the Catch scope so run history, owner alerts and monitoring still see a failure.

What is the default retry policy for Power Automate actions?

It depends on the flow's performance profile. Microsoft documents up to two retries at exponentially increasing intervals for the Low profile, and up to 12 retries for Medium and High. You can change the policy per action in its settings to exponential, fixed or none.

Power AutomateScopesMicrosoft TeamsDataverseApplication Insights
  1. Build a multi-stage approval workflow with Power Automate and SharePoint

    Automate manager and finance approvals on SharePoint list items with the Power Automate Approvals connector, write decisions back to the list, and handle Teams, timeouts and the 30-day limit.

    Architecture13 min read
  2. Dataverse Integration Choices: Webhooks vs Service Bus vs Plug-ins vs Flows

    Compare Dataverse plug-ins, webhooks, Azure Service Bus endpoints and Power Automate triggers for sending events to external systems, with limits, failure behaviour and setup steps.

    Architecture12 min read
  3. Power Platform ALM: ship apps and flows with managed solutions and pipelines

    Move Power Apps and Power Automate flows from development to test to production with managed solutions, environment variables, connection references and Power Platform pipelines.

    Architecture13 min read