Catch errors

When a workflow throws an error during execution, it can be caught and handled.

Use a try/except structure

You can use a try/except structure for error handling. For example:

YAML

  - STEP_NAME:
     try:
         call: http.get
         ...
     except:
         as: ERROR_MAP
         steps:
             ...

JSON

  [
    {
      "STEP_NAME": {
        "try": {
          "call": "http.get"
          ...
        },
        "except": {
          "as": "ERROR_MAP",
          "steps":
              ...
        }
      }
    }
  ]

Replace ERROR_MAP with the name of a map variable that contains the error message.

For example, the map for an HttpError error (with an HTTP status code >= 400) has the following attributes:

  • tags: HttpError string
  • message: human-readable error message
  • code: HTTP status code
  • headers: response headers
  • body: response body

For other error tags, see Workflow errors.

Note that the steps block is optional. It can contain the following:

  • assign
  • call
  • for
  • parallel
  • raise
  • return
  • steps
  • switch
  • try

Variable scope

A variable created inside an except block belongs to the local scope of that block, and is accessible only in that scope. For details, see Variables.

If you are assigning a variable inside an except block and want to access the variable outside of the block, assign the variable before the block to place it in the surrounding scope. For details, see Share a variable before an except block.

Example of try/except structure

For example, Workflows considers any HTTP request that returns status code 400 or greater failed. This makes the workflow execution fail unless the workflow catches and handles the error:

YAML

  - read_item:
      try:
          call: http.get
          args:
              url: https://host.com/api
          result: api_response
      except:
          as: e
          steps:
              - known_errors:
                  switch:
                  - condition: ${not("HttpError" in e.tags)}
                    return: "Connection problem."
                  - condition: ${e.code == 404}
                    return: "Sorry, URL wasn't found."
                  - condition: ${e.code == 403}
                    return: "Authentication error."
              - unhandled_exception:
                  raise: ${e}
  - url_found:
      return: ${api_response.body}

JSON

  [
    {
      "read_item": {
        "try": {
          "call": "http.get",
          "args": {
            "url": "https://host.com/api"
          },
          "result": "api_response"
        },
        "except": {
          "as": "e",
          "steps": [
            {
              "known_errors": {
                "switch": [
                  {
                    "condition": "${not(\"HttpError\" in e.tags)}",
                    "return": "Connection problem."
                  },
                  {
                    "condition": "${e.code == 404}",
                    "return": "Sorry, URL wasn't found."
                  },
                  {
                    "condition": "${e.code == 403}",
                    "return": "Authentication error."
                  }
                ]
              }
            },
            {
              "unhandled_exception": {
                "raise": "${e}"
              }
            }
          ]
        }
      }
    },
    {
      "url_found": {
        "return": "${api_response.body}"
      }
    }
  ]

The try block can contain multiple steps, allowing them to share the same except block for error handling:

YAML

  - read_item:
      try:
          steps:
              - step_a:
                  call: http.get
                  args:
                      url: https://host.com/api
                  result: api_response1
              - step_b:
                  call: http.get
                  args:
                      url: https://host.com/api2
                  result: api_response2
                  ...
      except:
          as: e
          steps:
              - KnownErrors:
              ...

JSON

  [
    {
      "read_item": {
        "try": {
          "steps": [
            {
              "step_a": {
                "call": "http.get",
                "args": {
                  "url": "https://host.com/api"
                },
                "result": "api_response1"
              }
            },
            {
              "step_b": {
                "call": "http.get",
                "args": {
                  "url": "https://host.com/api2"
                },
                "result": "api_response2"
                ...
              }
            }
          ]
        },
        "except": {
          "as": "e",
          "steps": [
            {
              "KnownErrors": null
            }
            ...
          ]
        }
      }
    }
  ]

Use a try/retry/except structure

You can also use a try/retry/except structure for error handling. For example:

YAML

  - step_name:
      try:
          steps:
              - step_a:
                  call: http.get
                  args:
                      url: https://httpstat.us/404
      retry: ${http.default_retry_non_idempotent}
      except:
          as: e
          steps:
              - checkForTimeout:
                  switch:
                  - condition: ${e.code == 404}
                    return: "notfound_404"
                  - condition: ${e.code == 408}
                    return: "timeout_408"
              - raiseError:
                  raise: ${e}
  - returnSuccess:
      return: "Success"

JSON

  [
    {
      "step_name": {
        "try": {
          "steps": [
            {
              "step_a": {
                "call": "http.get",
                "args": {
                  "url": "https://httpstat.us/404"
                }
              }
            }
          ]
        },
        "retry": "${http.default_retry_non_idempotent}",
        "except": {
          "as": "e",
          "steps": [
            {
              "checkForTimeout": {
                "switch": [
                  {
                    "condition": "${e.code == 404}",
                    "return": "notfound_404"
                  },
                  {
                    "condition": "${e.code == 408}",
                    "return": "timeout_408"
                  }
                ]
              }
            },
            {
              "raiseError": {
                "raise": "${e}"
              }
            }
          ]
        }
      }
    },
    {
      "returnSuccess": {
        "return": "Success"
      }
    }
  ]

For more information, see Retry steps.

Catch and handle HTTP request errors

This sample implements a custom exception handler based on the HTTP status code returned by the GET request. The workflow catches a potential exception and returns a predefined error message. If an exception is not recognized, the workflow execution fails and throws the exception as returned by the GET request.

The same pattern could be used to catch exceptions raised by Cloud Run or Cloud Run functions workloads.

YAML

# Use a custom exception handler to catch exceptions and return predefined
# error messages; if the exception isn't recognized, the workflow
# execution fails and throws the exception returned by the GET request
- read_item:
    try:
      call: http.get
      args:
        url: https://example.com/someapi
        auth:
          type: OIDC
      result: API_response
    except:
      as: e
      steps:
        - known_errors:
            switch:
              - condition: ${not("HttpError" in e.tags)}
                next: connection_problem
              - condition: ${e.code == 404}
                next: url_not_found
              - condition: ${e.code == 403}
                next: auth_problem
        - unhandled_exception:
            raise: ${e}
- url_found:
    return: ${API_response.body}
- connection_problem:
    return: "Connection problem; check URL"
- url_not_found:
    return: "Sorry, URL wasn't found"
- auth_problem:
    return: "Authentication error"

JSON

[
  {
    "read_item": {
      "try": {
        "call": "http.get",
        "args": {
          "url": "https://example.com/someapi",
          "auth": {
            "type": "OIDC"
          }
        },
        "result": "API_response"
      },
      "except": {
        "as": "e",
        "steps": [
          {
            "known_errors": {
              "switch": [
                {
                  "condition": "${not(\"HttpError\" in e.tags)}",
                  "next": "connection_problem"
                },
                {
                  "condition": "${e.code == 404}",
                  "next": "url_not_found"
                },
                {
                  "condition": "${e.code == 403}",
                  "next": "auth_problem"
                }
              ]
            }
          },
          {
            "unhandled_exception": {
              "raise": "${e}"
            }
          }
        ]
      }
    }
  },
  {
    "url_found": {
      "return": "${API_response.body}"
    }
  },
  {
    "connection_problem": {
      "return": "Connection problem; check URL"
    }
  },
  {
    "url_not_found": {
      "return": "Sorry, URL wasn't found"
    }
  },
  {
    "auth_problem": {
      "return": "Authentication error"
    }
  }
]

What's next