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." 🐦🐦