Exception Handling in Workflows in JS/TS

Hi team, I’m trying to throw an error from my activity and I need to pass that exception to my client. However when I throw an exception, I get the following


Ideally I’d want to relay the error thrown from the activity to the client but in this case I’m not able to. Any suggestions in solving this would be appreciated.

Moreover, I want to stop the workflow execution when the activity fails. I have set the retry { maxAttempts: 1} in proxActivites but the workflow keeps in running state. What could be the reason for this ?

Can you share your full json history:

tctl wf show -w <wfid> -r <runid> --of myhistory.json

(and share contents of myhistory.json)

I want to stop the workflow execution when the activity fails
Catch activity failure in workflow code and then return response from your workflow function (which would complete its execution).

What could be the reason for this ?

Looking at your wf history could help, if you can also show your workflow code that invokes the activity and activity options that would help.

1. Activity

Catching in the activity

Workflow Code

myhistory.json

{
 "events": [
  {
   "eventId": "1",
   "eventTime": "2022-11-10T20:04:39.347544991Z",
   "eventType": "WorkflowExecutionStarted",
   "taskId": "13631494",
   "workflowExecutionStartedEventAttributes": {
    "workflowType": {
     "name": "tokenValidate"
    },
    "taskQueue": {
     "name": "auth",
     "kind": "Normal"
    },
    "input": {
     "payloads": [
      {
       "metadata": {
        "encoding": "anNvbi9wbGFpbg=="
       },
       "data": "eyJwcm9qZWN0SUQiOiJwcm9qZWN0bGE2dmxnNzAwNHdxMGtoemd4dzI4aDJvIiwidG9rZW5JRCI6IjQ2NzNiMmMyNzU1N2EzNTQ4MjZmMzk3MTAyM2Q4YmYyMjBmZjY0YmMxZTNlMGJiYzZmNGQzNzI3Yjk4ZThlNTIifQ=="
      }
     ]
    },
    "workflowExecutionTimeout": "0s",
    "workflowRunTimeout": "0s",
    "workflowTaskTimeout": "10s",
    "originalExecutionRunId": "e61c9fd6-e425-47fb-8d43-a797724d0237",
    "identity": "tctl@dev-vm",
    "firstExecutionRunId": "e61c9fd6-e425-47fb-8d43-a797724d0237",
    "attempt": 1,
    "firstWorkflowTaskBackoff": "0s",
    "header": {

    }
   }
  },
  {
   "eventId": "2",
   "eventTime": "2022-11-10T20:04:39.347615321Z",
   "eventType": "WorkflowTaskScheduled",
   "taskId": "13631495",
   "workflowTaskScheduledEventAttributes": {
    "taskQueue": {
     "name": "auth",
     "kind": "Normal"
    },
    "startToCloseTimeout": "10s",
    "attempt": 1
   }
  },
  {
   "eventId": "3",
   "eventTime": "2022-11-10T20:05:13.467937894Z",
   "eventType": "WorkflowTaskStarted",
   "taskId": "13631504",
   "workflowTaskStartedEventAttributes": {
    "scheduledEventId": "2",
    "identity": "315007@dev-vm",
    "requestId": "5d938acc-68d6-48f8-8af7-f09464f3b1ae"
   }
  },
  {
   "eventId": "4",
   "eventTime": "2022-11-10T20:05:13.595451224Z",
   "eventType": "WorkflowTaskCompleted",
   "taskId": "13631508",
   "workflowTaskCompletedEventAttributes": {
    "scheduledEventId": "2",
    "startedEventId": "3",
    "identity": "315007@dev-vm",
    "binaryChecksum": "@temporalio/worker@1.4.4+2ea73fd4b965a8a052cfd0d972795c9750d9ef2be7370a970826cd601f7f2b13"
   }
  },
  {
   "eventId": "5",
   "eventTime": "2022-11-10T20:05:13.595586034Z",
   "eventType": "ActivityTaskScheduled",
   "taskId": "13631509",
   "activityTaskScheduledEventAttributes": {
    "activityId": "1",
    "activityType": {
     "name": "validateToken"
    },
    "taskQueue": {
     "name": "auth",
     "kind": "Normal"
    },
    "header": {

    },
    "input": {
     "payloads": [
      {
       "metadata": {
        "encoding": "anNvbi9wbGFpbg=="
       },
       "data": "eyJwcm9qZWN0SUQiOiJwcm9qZWN0bGE2dmxnNzAwNHdxMGtoemd4dzI4aDJvIiwidG9rZW5JRCI6IjQ2NzNiMmMyNzU1N2EzNTQ4MjZmMzk3MTAyM2Q4YmYyMjBmZjY0YmMxZTNlMGJiYzZmNGQzNzI3Yjk4ZThlNTIifQ=="
      },
      {
       "metadata": {
        "encoding": "YmluYXJ5L251bGw="
       }
      }
     ]
    },
    "scheduleToCloseTimeout": "0s",
    "scheduleToStartTimeout": "0s",
    "startToCloseTimeout": "60s",
    "heartbeatTimeout": "0s",
    "workflowTaskCompletedEventId": "4",
    "retryPolicy": {
     "initialInterval": "1s",
     "backoffCoefficient": 2,
     "maximumInterval": "100s",
     "maximumAttempts": 1
    }
   }
  },
  {
   "eventId": "6",
   "eventTime": "2022-11-10T20:05:13.615673622Z",
   "eventType": "ActivityTaskStarted",
   "taskId": "13631514",
   "activityTaskStartedEventAttributes": {
    "scheduledEventId": "5",
    "identity": "315007@dev-vm",
    "requestId": "0fba2683-8c4d-44c2-a6de-612d7fc406ff",
    "attempt": 1
   }
  },
  {
   "eventId": "7",
   "eventTime": "2022-11-10T20:05:13.644723350Z",
   "eventType": "ActivityTaskFailed",
   "taskId": "13631515",
   "activityTaskFailedEventAttributes": {
    "failure": {
     "message": "No token is sent.",
     "source": "TypeScriptSDK",
     "stackTrace": "undefined",
     "applicationFailureInfo": {
      "type": "Object"
     }
    },
    "scheduledEventId": "5",
    "startedEventId": "6",
    "identity": "315007@dev-vm",
    "retryState": "MaximumAttemptsReached"
   }
  },
  {
   "eventId": "8",
   "eventTime": "2022-11-10T20:05:13.644731639Z",
   "eventType": "WorkflowTaskScheduled",
   "taskId": "13631516",
   "workflowTaskScheduledEventAttributes": {
    "taskQueue": {
     "name": "315007@dev-vm-auth-0d2dd5b43d1c44549d5f120763c6fdbe",
     "kind": "Sticky"
    },
    "startToCloseTimeout": "10s",
    "attempt": 1
   }
  },
  {
   "eventId": "9",
   "eventTime": "2022-11-10T20:05:13.660025238Z",
   "eventType": "WorkflowTaskStarted",
   "taskId": "13631520",
   "workflowTaskStartedEventAttributes": {
    "scheduledEventId": "8",
    "identity": "315007@dev-vm",
    "requestId": "978f6215-b0c5-4a47-82c6-55008b88b513"
   }
  },
  {
   "eventId": "10",
   "eventTime": "2022-11-10T20:05:13.687923185Z",
   "eventType": "WorkflowTaskFailed",
   "taskId": "13631524",
   "workflowTaskFailedEventAttributes": {
    "scheduledEventId": "8",
    "startedEventId": "9",
    "failure": {
     "message": "{\"err\":{\"cause\":{\"name\":\"ApplicationFailure\",\"type\":\"Object\",\"nonRetryable\":false,\"details\":[],\"failure\":{\"message\":\"No token is sent.\",\"source\":\"TypeScriptSDK\",\"stackTrace\":\"undefined\",\"applicationFailureInfo\":{\"type\":\"Object\"}}},\"name\":\"ActivityFailure\",\"activityType\":\"validateToken\",\"activityId\":\"1\",\"retryState\":4,\"identity\":\"315007@dev-vm\",\"failure\":{\"message\":\"Activity task failed\",\"cause\":{\"message\":\"No token is sent.\",\"source\":\"TypeScriptSDK\",\"stackTrace\":\"undefined\",\"applicationFailureInfo\":{\"type\":\"Object\"}},\"activityFailureInfo\":{\"scheduledEventId\":\"5\",\"startedEventId\":\"6\",\"identity\":\"315007@dev-vm\",\"activityType\":{\"name\":\"validateToken\"},\"activityId\":\"1\",\"retryState\":\"RETRY_STATE_MAXIMUM_ATTEMPTS_REACHED\"}}}} [A non-Error value was thrown from your code. We recommend throwing Error objects so that we can provide a stack trace]",
     "source": "TypeScriptSDK"
    },
    "identity": "315007@dev-vm",
    "binaryChecksum": "@temporalio/worker@1.4.4+2ea73fd4b965a8a052cfd0d972795c9750d9ef2be7370a970826cd601f7f2b13"
   }
  },
  {
   "eventId": "11",
   "eventTime": "2022-11-10T20:05:13.687932096Z",
   "eventType": "WorkflowTaskScheduled",
   "taskId": "13631525",
   "workflowTaskScheduledEventAttributes": {
    "taskQueue": {
     "name": "auth",
     "kind": "Normal"
    },
    "startToCloseTimeout": "10s",
    "attempt": 1
   }
  },
  {
   "eventId": "12",
   "eventTime": "2022-11-10T20:05:13.702153144Z",
   "eventType": "WorkflowTaskStarted",
   "taskId": "13631528",
   "workflowTaskStartedEventAttributes": {
    "scheduledEventId": "11",
    "identity": "315007@dev-vm",
    "requestId": "886ced36-7d65-4c0f-8638-f876407e27e7"
   }
  },
  {
   "eventId": "13",
   "eventTime": "2022-11-10T20:05:13.784559157Z",
   "eventType": "WorkflowTaskFailed",
   "taskId": "13631532",
   "workflowTaskFailedEventAttributes": {
    "scheduledEventId": "11",
    "startedEventId": "12",
    "failure": {
     "message": "{\"err\":{\"cause\":{\"name\":\"ApplicationFailure\",\"type\":\"Object\",\"nonRetryable\":false,\"details\":[],\"failure\":{\"message\":\"No token is sent.\",\"source\":\"TypeScriptSDK\",\"stackTrace\":\"undefined\",\"applicationFailureInfo\":{\"type\":\"Object\"}}},\"name\":\"ActivityFailure\",\"activityType\":\"validateToken\",\"activityId\":\"1\",\"retryState\":4,\"identity\":\"315007@dev-vm\",\"failure\":{\"message\":\"Activity task failed\",\"cause\":{\"message\":\"No token is sent.\",\"source\":\"TypeScriptSDK\",\"stackTrace\":\"undefined\",\"applicationFailureInfo\":{\"type\":\"Object\"}},\"activityFailureInfo\":{\"scheduledEventId\":\"5\",\"startedEventId\":\"6\",\"identity\":\"315007@dev-vm\",\"activityType\":{\"name\":\"validateToken\"},\"activityId\":\"1\",\"retryState\":\"RETRY_STATE_MAXIMUM_ATTEMPTS_REACHED\"}}}} [A non-Error value was thrown from your code. We recommend throwing Error objects so that we can provide a stack trace]",
     "source": "TypeScriptSDK"
    },
    "identity": "315007@dev-vm",
    "binaryChecksum": "@temporalio/worker@1.4.4+2ea73fd4b965a8a052cfd0d972795c9750d9ef2be7370a970826cd601f7f2b13"
   }
  }
 ]
}

Thanks for the info.

In your workflow code change

throw {
   err
}

to

throw err

Your activity failure after retries is going to deliver ActivityFailure to your workflow so your err you catch in workflow code is going to have type ActivityFailure. If you rethrow ActivityFailure itself from workflow code it will fail workflow execution. When you wrap it in { } it will throw a different type of error (and i think this is bad practice imo) and by default Temporal on intermittent failures does not fail workflow execution but blocks it (and replays the workflow task like you see in your wf history) to allow you to fix your code, restart workers and apply the fix.

We also recommend in general throwing Error objects, not plain objects like this one:

image

Specifically, usually instances of the ApplicationFailure (which is an Error).

import { ApplicationFailure } from '@temporalio/activity'

...

  if (!token) throw ApplicationFailure.create({ nonRetryable: true, message: 'No token is sent.', type: 'TOKEN-INVALID' })

(nonRetryable if you don’t want the Activity to be retried)

+1 to using non retryable application failures as Lauren mentioned and JS Error objects as opposed to plain JS objects so the stack trace can be captured.
I wouldn’t necessarily use application failures for retryable errors though, those are optional.

Thanks for the help, however, the problem still remains in handling the error at the client side. When I throw an error from the workflow, the client code stops at line 22. It should technically go to catch () in the client but it’s not

I’ve noticed another thing , that it keeps on retrying the workflow and the workflow remains in running state.

1. Client Side

2. throwing error from Workflow

I even tried throwing error like this

catch (err) {
    throw {
      message: err.failure.cause.message,
      type: err.cause.type
    };
  }

but doesn’t work

Workflows dont have a default retry policy, so no need to set retry->maximumAttempts in your client code.

Don’t throw WorkflowError from your workflow code, this will cause the default behavior for intermittent errors which is to block wf execution and replay the workflow task waiting for you to fix the error, redeploy worker(s) so wf execution can continue (you can see this in your workflow history that you pasted).

If you want to throw an error from workflow that fails workflow execution you could do for example:

 throw ApplicationFailure.create({
  message: "..."
});

and then can catch err in client code, the error should be type WorkflowFailedError.

Hope this helps.

Thanks for the help. Really appreciate the response of the community on this. Hoping to bug you soon with a more interesting issue.