Symptom
A workflow that ran for months starts showing Failed runs. Downstream teams report missing files, tickets or messages. Sometimes the run doesn't fail outright. It shows Succeeded while one branch was skipped, or it sits in Running until it times out.
Work through it in this order: find the action that actually failed, read its error, match it to a cause, fix that cause, then resubmit only the runs that need it.
Find the failed runs
From the portal
Open the logic app, then Overview > Runs history (Consumption) or Workflows > workflow > Run history (Standard). Filter on Failed and open a run. The failing action has a red icon. Expand it and read Outputs. The status code and error body are there. A parent scope, condition or loop can show as failed only because a child action failed, so keep expanding until you reach the innermost red action.
From the CLI (Consumption)
az rest calls the ARM API directly, so it works without the logic CLI extension:
SUB=$(az account show --query id -o tsv)
RG=rg-integration; WF=la-invoice-sync
BASE="https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Logic/workflows/$WF"
# Last failed runs
az rest --method get \
--url "$BASE/runs?api-version=2016-06-01&\$filter=status eq 'Failed'&\$top=20" \
--query "value[].{run:name, start:properties.startTime, code:properties.code}" -o table
# Actions in one run, with status and code
RUN=08584... # run name from the list above
az rest --method get --url "$BASE/runs/$RUN/actions?api-version=2016-06-01" \
--query "value[?properties.status=='Failed'].{action:name, code:properties.code, error:properties.error.message}" -o table
For Standard logic apps the run history lives in the app's own runtime, not in Microsoft.Logic/workflows, so use the portal or the Standard host APIs instead of these calls.
From Log Analytics
When diagnostic settings send WorkflowRuntime logs to a workspace, you can query failures across every workflow at once. Consumption writes to AzureDiagnostics:
AzureDiagnostics
| where TimeGenerated > ago(24h)
| where ResourceProvider == "MICROSOFT.LOGIC" and Category == "WorkflowRuntime"
| where OperationName == "Microsoft.Logic/workflows/workflowActionCompleted"
| where status_s == "Failed"
| summarize failures = count(), lastSeen = max(TimeGenerated)
by workflow = resource_workflowName_s, action = resource_actionName_s, code = code_s
| order by failures desc
Standard logic apps write to the resource-specific LogicAppWorkflowRuntime table. Column names in AzureDiagnostics depend on which fields have been logged, so run AzureDiagnostics | where ResourceProvider == "MICROSOFT.LOGIC" | getschema if a column above is missing in your workspace.
Map the error to a cause
| Error in the action output | Likely cause | Fix |
|---|---|---|
401 Unauthorized, "The API connection is not authorized" | API connection token expired or the user who authorized it left or changed their password | Re-authorize the connection, or move it to a managed identity or service principal |
403 Forbidden from an Azure resource | Managed identity lost its role assignment, or the target firewall blocks the logic app | Restore the role at the right scope; allow the outbound IPs or use private networking |
404 NotFound | Target file, queue, list item or URL was renamed or deleted | Correct the path or parameter; add a condition for "not found" if it's expected |
429 Too Many Requests | Connector or target API throttling | Lower concurrency, add a retry policy, or spread the load across connections |
ActionTimedOut or GatewayTimeout | Target slower than the action timeout, or an on-premises data gateway is offline | Check the gateway service and host; use the async (202 + polling) pattern for long jobs |
InvalidTemplate | Expression failed on null or unexpected data, such as body('Parse_JSON')?['id'] missing | Guard with coalesce() or ?, validate the input schema |
ActionFailed on a scope or loop | A child action failed | Expand the scope and fix the child action |
Skipped actions | runAfter only covers Succeeded | Add explicit error-handling branches (see below) |
Fix the common causes
Expired or broken API connections
List the API connections in the resource group and their status:
az resource list -g $RG --resource-type Microsoft.Web/connections \
--query "[].{name:name, status:properties.overallStatus}" -o table
az resource show -g $RG -n office365-conn --resource-type Microsoft.Web/connections \
--query "properties.statuses" -o json
A status of Error with a message about the token means the connection needs re-authorizing. Open API connections > connection > Edit API connection > Authorize, sign in with a service account (not a personal account), and save. Runs that failed before the fix still need to be resubmitted.
Tip
Connections authorized by a named person break when that person leaves or their password changes. Where the connector supports it, use a managed identity, or a dedicated service account excluded from password expiry and documented as an owner of the workflow.
Managed identity and RBAC
If the workflow calls Azure resources with its managed identity, confirm the identity still has the role:
PID=$(az resource show -g $RG -n $WF --resource-type Microsoft.Logic/workflows --query identity.principalId -o tsv)
az role assignment list --assignee $PID --all -o table
An empty list after someone tidied up role assignments explains a sudden 403. Re-create the assignment at the narrowest scope that works.
Throttling and concurrency
A for each loop runs iterations in parallel by default, and a burst of triggers can start many runs at once. Both can push a connector or API over its limit. In the designer, open the loop's or trigger's Settings and set Concurrency control to a lower degree of parallelism. Add a retry policy to the action (exponential interval) so short throttling windows recover by themselves. Check the current connector limits in the connector reference rather than guessing.
Resubmit failed runs safely
Resubmitting replays the original trigger input as a new run. Every action runs again, including the ones that already succeeded the first time.
Warning
If the failed run already sent an email, posted a payment or inserted rows before the failing action, a resubmit does it again. Check which actions succeeded in the original run, and confirm the target system tolerates a repeat before you resubmit.
Resubmit one run from the portal (Resubmit on the run toolbar), or through the trigger history API. For Consumption workflows, the trigger history name is the same as the run name:
TRIGGER=When_a_blob_is_added_or_modified
az rest --method post \
--url "$BASE/triggers/$TRIGGER/histories/$RUN/resubmit?api-version=2016-06-01"
To resubmit a batch after a fix, loop over the failed runs from a known window, and test with one run first:
for RUN in $(az rest --method get \
--url "$BASE/runs?api-version=2016-06-01&\$filter=status eq 'Failed' and startTime ge 2026-11-09T22:00:00Z" \
--query "value[].name" -o tsv); do
echo "Resubmitting $RUN"
az rest --method post --url "$BASE/triggers/$TRIGGER/histories/$RUN/resubmit?api-version=2016-06-01"
done
Runs stuck in Running can be cancelled with POST $BASE/runs/$RUN/cancel?api-version=2016-06-01 before you resubmit them.
Prevent it next time
- Handle errors in the workflow. Put the main logic in a Scope, then add a second scope that runs after the first has failed or has timed out. Use it to log the error and notify the owner.
- Make actions idempotent. Use upserts, check whether a record exists before creating it, or pass a stable key such as the trigger's message ID. Then a resubmit is always safe.
- Alert on failures. Create a metric alert on Runs Failed (Consumption) for each production workflow, or a log alert on the KQL query above across all of them.
- Track connection health. Review
Microsoft.Web/connectionswith an Error status weekly, or run it as an Azure Resource Graph query. - Keep diagnostic settings on for every production logic app so failures can be queried after the run history retention period ends.