Try, Catch, Finally



I started adding Try, Catch, and Finally scopes to my Power Automate flows for a very simple reason.

Things break.

Sometimes a SharePoint action fails. Sometimes a user lookup fails. Sometimes an approval process doesn't behave the way I expect. Most of the time the failure isn't the real problem. The real problem is discovering a broken flow later because nobody knew it failed in the first place.

Like most people, my first attempts at error handling were very flow-specific. If a flow failed, I would add a few actions to capture information about that particular process. It worked, but it also created a new problem.

Every flow needed its own custom error handling.

The more flows I built, the less appealing that approach became.

What I really wanted was a reusable Catch scope that could be copied and pasted into any flow with minimal effort. The challenge was figuring out how a generic Catch block could determine what failed without knowing anything about the actions inside the Try scope.

The breakthrough came from the Power Automate expression:

result('Try')

As long as the Try scope is named Try, the expression returns an array containing information about every action that executed inside that scope. The Catch logic doesn't need to know whether the flow is processing SharePoint items, sending approvals, loading files, or executing SQL statements. It simply inspects the results of the Try scope.

Once the results were available, I used a Filter Array action to locate actions with a status of Failed. Since the first failure is usually the root cause of the problem, I then used a Compose action to retrieve the first failing action from the filtered array.

That single object contains a surprising amount of useful information.

Flow name.

Failed action name.

Status.

Error code.

Error details.

The next challenge was identifying where the failure occurred. Since I maintain separate Development, Test, and Production environments, that information matters. A flow failure in Development is usually an inconvenience. A flow failure in Production tends to be more interesting.

To capture the environment, I used:

workflow()?['Tags']?['environmentName']

The Catch scope then queries a governance list to retrieve the environment display name and includes it in the alert notification.

When a flow fails, a High Importance email is sent containing the environment, flow name, failed action, status, error code, and error message. The same information is also written to a FailedFlows list on my Power Platform Governance Team site.

The email provides immediate visibility.

The SharePoint list provides historical visibility.

Over time that list becomes a useful repository for identifying recurring problems and tracking flow reliability across environments.

The most encouraging part of this project was discovering how reusable the solution became. Once the framework was complete, I could copy and paste the entire Catch scope into another flow and it immediately worked.

Well... mostly.

There was one small surprise.

Different connectors store error messages differently. Initially I assumed every failed action would expose its error details in the same location. It didn't take long to discover that assumption was incorrect.

One action might return an error message in:

outputs.body.message

Another action might return the message in:

outputs.body.error.message

And yet another action may decide to store its error details somewhere completely different because Microsoft enjoys keeping life interesting.

The interesting part is that this isn't just a difference between flows. Two actions within the same Try scope can return their error information differently. The Catch framework can identify the failed action, status, error code, flow name, and environment consistently, but the location of the error message itself may vary depending on the connector that generated it.

C'mon Microsoft!

At first that felt like a limitation. The more I thought about it, however, the less concerned I became. The Catch framework still provides almost everything necessary to begin troubleshooting. Even if the error message comes back blank, I still know which environment experienced the failure, which flow failed, which action failed, and the associated error code.

Most of the time that's enough information to locate the failing run and begin investigating immediately.

If the only modification required when copying the Catch scope into a new flow is updating a single expression after discovering where a particular connector stores its error details, that's still far better than rebuilding an entire error-handling process from scratch every time.

Looking back, the most valuable part of this exercise wasn't the email notification or the SharePoint logging. It was realizing that a largely reusable Catch framework was possible at all.

The Try scopes vary from flow to flow because the business processes are different. The Catch scope, however, remains remarkably consistent. Once I understood how to interrogate the results of a failed Try scope, the rest fell into place.

Not bad for a project that started with, "I should probably add some error handling." 🐦🐦