fix(core): Construct execution errors correctly when Error.prototype is frozen (#33897)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
José Braulio González Valido
2026-07-11 18:34:50 +01:00
committed by GitHub
parent 3bc2292c78
commit fffa4233b3
4 changed files with 75 additions and 4 deletions
@@ -27,7 +27,13 @@ export abstract class ExecutionBaseError extends BaseError {
constructor(message: string, options: ExecutionBaseErrorOptions = {}) {
super(message, options);
this.name = this.constructor.name;
// Defined, not assigned: assignment throws when `Error.prototype` is frozen (task runner secure mode)
Object.defineProperty(this, 'name', {
value: this.constructor.name,
writable: true,
enumerable: true,
configurable: true,
});
this.timestamp = Date.now();
const { cause, errorResponse } = options;
@@ -24,8 +24,17 @@ export class WorkflowActivationError extends ExecutionBaseError {
let error = cause as Error;
if (cause instanceof ExecutionBaseError) {
error = new Error(cause.message);
error.constructor = cause.constructor;
error.name = cause.name;
// Defined, not assigned: a fresh `Error` has no own `constructor`/`name`, so
// assignment throws when `Error.prototype` is frozen (task runner secure mode)
Object.defineProperties(error, {
constructor: {
value: cause.constructor,
writable: true,
enumerable: true,
configurable: true,
},
name: { value: cause.name, writable: true, enumerable: true, configurable: true },
});
error.stack = cause.stack;
}
super(message, { cause: error });
@@ -0,0 +1,28 @@
import { ExpressionError } from '../../src/errors';
describe('ExecutionBaseError', () => {
it('should set name to the concrete class name', () => {
const error = new ExpressionError('message');
expect(error.name).toBe('ExpressionError');
});
describe('with a frozen Error.prototype', () => {
const nameDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'name')!;
beforeAll(() => {
Object.defineProperty(Error.prototype, 'name', { ...nameDescriptor, writable: false });
});
afterAll(() => {
Object.defineProperty(Error.prototype, 'name', nameDescriptor);
});
it('should construct and set name without writing through the prototype', () => {
const error = new ExpressionError('Paired item data is unavailable');
expect(error.name).toBe('ExpressionError');
expect(Object.getOwnPropertyDescriptor(error, 'name')?.writable).toBe(true);
});
});
});
@@ -1,4 +1,4 @@
import { WorkflowActivationError } from '../../src/errors';
import { WorkflowActivationError, WorkflowOperationError } from '../../src/errors';
describe('WorkflowActivationError', () => {
it('should default to `error` level', () => {
@@ -44,4 +44,32 @@ describe('WorkflowActivationError', () => {
expect(error.description).toBe('Actionable detail');
});
describe('with a frozen Error.prototype', () => {
const descriptors = {
name: Object.getOwnPropertyDescriptor(Error.prototype, 'name')!,
constructor: Object.getOwnPropertyDescriptor(Error.prototype, 'constructor')!,
};
beforeAll(() => {
for (const [key, descriptor] of Object.entries(descriptors)) {
Object.defineProperty(Error.prototype, key, { ...descriptor, writable: false });
}
});
afterAll(() => {
for (const [key, descriptor] of Object.entries(descriptors)) {
Object.defineProperty(Error.prototype, key, descriptor);
}
});
it('should wrap an ExecutionBaseError cause without writing through the prototype', () => {
const operationCause = new WorkflowOperationError('operation failed');
const error = new WorkflowActivationError('activation failed', { cause: operationCause });
expect(error.name).toBe('WorkflowActivationError');
expect(error.description).toBe('operation failed');
});
});
});