Possible bug using Stop and Error node with type of Error Object?

The Problem

I’m building a sub-workflow that wraps some RESTful API calls and at the end either returns a successful response or uses Stop and Error to throw the error to the parent workflow.

I’d like the parent workflow to be able to further handle the error, however all my attempts to pass an object using Error Type: Error Object have failed. It seems like this ‘object’ is always getting stringified which differs from my experiences with most other nodes. I have a strong suspicion this is a bug.

What I’m seeing

To illustrate the issue, here are several attempts to use the Stop and Error node along with the output received by the parent workflow node.

:white_check_mark: When using Error Type: Error Message, the parent receives an ‘error’ value with the message string. This is as I’d expect.

:white_check_mark: I can also use an expression along with Error Type: Error Message to populate the message or stringify the json. The behavior here also feels correct.

:cross_mark: Now if I switch to Error Type: Error Object, my expectation would be that rather than ”error”: “message” I’d receive output along the lines of ”error”: {…}. Here’s a very simple example, a string is a JSON object so:

Error Object: ”An Error has occurred.”

Expectation: ”error”: “An Error has occurred”

Reality: ”error”: “Error: \”An Error has occurred.\”” - an escaped JSON string, prefixed by an “Error:” string.

:cross_mark: I can’t even construct my own simple JSON error object:
Error Object: { "myMessage": "An Error has occurred." }

Expectation: “error": { "myMessage": "An Error has occurred." }

Reality: ”error": "Error: {\"myMessage\":\"An Error has occurred.\"}" - again, stringified JSON formatted into an “Error:” message string.

:cross_mark: It gets even worse when I try to use expressions within the Error Object. Any attempts to use or return javascript objects without stringifying them myself results in an error within the node.

Now, when Stop and Error is hit, it throws up a little toast notification with the error message stringified. That makes sense, but I’m wondering if maybe that stringified value is what’s getting passed onward instead of the actual intended Error Object? Or maybe I’m just doing something horribly wrong.

There does seem to be at least one other person who’s run into this: How does Error Objects works? - #3 by Wouter_Nigrini

At the end of the day, I realize i could probably serialize the JSON into the message and re-parse on the other side, but that feels hacky and if this is indeed a bug I’d like to see it get fixed.

The Workflows

This issue is really just with the Stop and Error node itself

For context, if it helps, here is the overall workflow concept I’m trying to build. Please note that I’ve made other notes/suggestions in there which I’ll probably start another thread for, but are outside the scope of this issue.

Information on your n8n setup

  • n8n version: 2.4.8
  • Database (default: SQLite): SQLite
  • n8n EXECUTIONS_PROCESS setting (default: own, main): own,main
  • Running n8n via (Docker, npm, n8n cloud, desktop app): Docker
  • Operating system: Ubuntu
1 Like

Hi @namante, welcome!

hmmm, i do not really know if it is a bug or not but what worked for me to preserve the structure is double stringifying while using the same error key:

{{
  JSON.stringify({
    "error": JSON.stringify({
      "code": "404",
      "text": "invalid message"
    })
  })
}}

This will still output a string:

However you can parse the JSON in the subsequent step to get the object back:

I also notice the you can just get the similar result by selecting Error Type > Error Message and passing a JSON object there, it outputs the same outcome.

So It seems that the error key is designed to return a JSON string rather than a raw object..

Hope this can add some context..

Hi @namante,

I think the issue you’re getting is that you are using non Error object property names which causes a JSON string to be returned instead of an Error Object. If you stick to the property names of the Error object, it should return correctly.

The key being the “message” instead of “myMessage”. This is because according to the documentation the Error Object option converts the error to a Javascript Error object.

Child workflow throwing errors;

Parent wrokflow calling sub workflow

Thank you for the suggestion, that will indeed work, however as I said in my first post: “At the end of the day, I realize I could probably serialize the JSON into the message and re-parse on the other side, but that feels hacky and if this is indeed a bug I’d like to see it get fixed"

1 Like

I may be misunderstanding you, but this doesn’t seem accurate and testing with your proposed solution does not fix the issue.

It sounds like you’re suggesting that the n8n Error Object option is actually returning a native JavaScript Error object - that does indeed have instance properties for cause and message, however I’m not finding any evidence that is the case.

The documentation for n8n’s concept of Error Object clearly says:

Error Object parameters

The Error Object Error Type adds one parameter, the Error Object. Enter a JSON object that contains the error properties you'd like to throw.

That would imply that any valid JSON should be able to be returned.

Just to make sure, I did test with your proposal:

Error Object: {
“cause”: “Some cause”,
“message”: “{{ $json.error.errorMessage }}”
}

Output: "error": "Error: {\"cause\":\"Some cause\",\"message\":\"\"}"

The issue is exactly the same as described above.

Hi @namante,

Ok I had to go fact check myself and yes it seems my understanding of the Error object was infact incorrect. According to the actual n8n codebase, it creates an object where “message” is either assign from “message”, “description” or “error” values if that is what you set in your object. It then further sets options with “description”, “type”, “level” and “metadata”.

So cause is not a valid param but message, description or error is if you want the error object to display “naturally” like in my screenshot.

However, when I pass in other params which does not contain message, description or error in the object, then it just returns a JSON string, especially if you did NOT include one of “message”, “description” or “error”, it’ll just stringify the entire object you passed

If you want to return a custom object to the calling workflow from the error node, then I would suggest doing it like below. Build your custom error object you want to return, then use the Error Message option and stringify the object. This will ovoid the Error Object option of prefixing the object with “Error: “

Thank you for linking directly to the offending code. That gave me a lot more context and explains why I feel like it’s a bug - it’s definitely a regression.

On 08/01/2025 this PR was merged: fix(Stop and Error Node): Show error message when error type is an object by ShireenMissi · Pull Request #17898 · n8n-io/n8n · GitHub

Fix: Stop and Error node empty error message for Error Object type

Problem
Stop and Error node with “Error Object” type was displaying empty error messages instead of extracting meaningful text from the JSON object.

Solution

  • Extract error message from object properties (message, description, error) with fallback to stringified object

  • Pass extracted message as string to NodeOperationError for proper display

  • Preserve original object data in error metadata

Prior to the PR, StopAndError.node.ts (8215e0b) looked like this:

        if (errorType === 'errorMessage') {
			toThrow = this.getNodeParameter('errorMessage', 0) as string;
		} else {
			const json = this.getNodeParameter('errorObject', 0) as string;

			const errorObject = jsonParse<JsonObject>(json);

			toThrow = {
				name: 'User-thrown error',
				message: `Workflow ID ${workflowId} "${workflowName}" has failed`,
				...errorObject,
			};
		}

		throw new NodeOperationError(this.getNode(), toThrow);

For type errorMessage , a string was thrown.

For type errorOject, a JSON object with a default name and message, and most importantly the expansion of the error object: ...errorObject. This would have thrown the full object including overriding the name/message if desired.

Now, StopAndError.node.ts (aced4bf) is greatly simplified:

        const errorType = this.getNodeParameter('errorType', 0) as 'errorMessage' | 'errorObject';
		const errorParameter =
			errorType === 'errorMessage'
				? (this.getNodeParameter('errorMessage', 0) as string)
				: (this.getNodeParameter('errorObject', 0) as string);

		const { message, options } = createErrorFromParameters(errorType, errorParameter);

		throw new NodeOperationError(this.getNode(), message, options);

Note the change in NodeOperationError. The second parameter, defined as error: Error | string | JsonObject, used to either be a string or object depending on the error type. Now only the string message is passed.

StopAndError/utils.ts parses out this ‘friendly message’ from the JSON object.

        const errorObject = jsonParse<JsonObject>(errorParameter);

		const errorMessage =
			(isString(errorObject.message) ? errorObject.message : '') ||
			(isString(errorObject.description) ? errorObject.description : '') ||
			(isString(errorObject.error) ? errorObject.error : '') ||
			`Error: ${JSON.stringify(errorObject)}`;

		return {
			message: errorMessage,
			options: {
				description: isString(errorObject.description) ? errorObject.description : undefined,
				type: isString(errorObject.type) ? errorObject.type : undefined,
				level: 'error',
				metadata: errorObject,
			},
		};

So if your object includes message, description, or error, it’ll throw that as a string. Otherwise it throws the entire json, stringified. It does return the full errorObject as 'metadata’, which gets set to this.context.metadata in the NodeOperationError object, but I’m not seeing if/how that’s ever exposed.

It seems to me like the correct behavior would be for StopAndError.node.ts to also receive the errorObject and expand it within the error message:

		const { message, options, errorObject } = createErrorFromParameters(errorType, errorParameter);

        let error;

        if (errorType === 'errorMessage') {
			error = message;
		} else {
			error = {
				message,
				...errorObject,
			};
		}

        throw new NodeOperationError(this.getNode(), error, options);
1 Like

After digging in a bit with the intent of making a PR, it seems like the NodeOperationError class never property passed along the expanded error object properties. I may poke at it a bit more but it’s not as simple of a fix as I’d hoped.

1 Like