Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,46 @@ describe('resolveInput', () => {
).toBe('{ "a": "str" }');
});

describe('CODE step output variable resolution', () => {
const codeStepContext = {
'transform-step-001': {
memberName: 'Mike Chen',
eventName: 'Microsoft Gala',
result: {
memberName: 'Mike Chen',
eventName: 'Microsoft Gala',
},
},
};

it('should resolve CODE step output via direct path', () => {
expect(
resolveInput(
'{{transform-step-001.memberName}}',
codeStepContext,
),
).toBe('Mike Chen');
});

it('should resolve CODE step output via result path', () => {
expect(
resolveInput(
'{{transform-step-001.result.memberName}}',
codeStepContext,
),
).toBe('Mike Chen');
});

it('should resolve multiple CODE step variables in a string', () => {
expect(
resolveInput(
'Confirmed - {{transform-step-001.result.memberName}} for {{transform-step-001.result.eventName}}',
codeStepContext,
),
).toBe('Confirmed - Mike Chen for Microsoft Gala');
});
});

describe('bracket notation for keys with special characters', () => {
it('should resolve variables with keys containing spaces', () => {
const contextWithSpaces = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,38 @@ describe('getWorkflowRunContext', () => {
const context = getWorkflowRunContext(stepInfos);

expect(context).toEqual({
step1: { res: 'value1' },
step2: {},
step4: { res: 0 },
step5: { res: undefined },
step1: { res: 'value1', result: { res: 'value1' } },
step2: { result: {} },
step4: { res: 0, result: { res: 0 } },
step5: { res: undefined, result: { res: undefined } },
});
});

it('allows accessing step output via result property for backward compatibility', () => {
const stepInfos: WorkflowRunStepInfos = {
'code-step-001': {
result: { memberName: 'Mike Chen', eventName: 'Gala' },
status: StepStatus.SUCCESS,
},
};

const context = getWorkflowRunContext(stepInfos);

// Direct access (used by UI-generated variables)
expect(
(context['code-step-001'] as Record<string, unknown>)['memberName'],
).toBe('Mike Chen');

// Access via result property (used by manually written variables)
expect(
(
(context['code-step-001'] as Record<string, unknown>)[
'result'
] as Record<string, unknown>
)['memberName'],
).toBe('Mike Chen');
});

it('returns an empty object when no step has a defined result', () => {
const stepInfos: WorkflowRunStepInfos = {
step1: { status: StepStatus.NOT_STARTED },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export const getWorkflowRunContext = (
return Object.fromEntries(
Object.entries(stepInfos)
.filter(([, value]) => isDefined(value?.['result']))
.map(([key, value]) => [key, value?.['result']]),
.map(([key, value]) => {
const result = value?.['result'];

if (typeof result === 'object' && result !== null) {
return [key, { ...result, result }];
}
Comment on lines +13 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The spread syntax { ...result, result } in getWorkflowRunContext overwrites the result property of a step's output object, causing data loss if that key already exists.
Severity: HIGH

Suggested Fix

To preserve the original result property if it exists, reverse the order of the properties in the object literal to { result, ...result }. This ensures that the spread properties from the result object will overwrite the initial result key, effectively preserving the original key-value pair from the step's output.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/twenty-shared/src/workflow/utils/getWorkflowRunContext.ts#L13-L15

Potential issue: In the `getWorkflowRunContext` function, when a step's output
(`result`) is an object, the code uses `{ ...result, result }` to construct the new
context. If the original `result` object already contains a property named `result`,
this syntax overwrites that property's value with the entire `result` object itself. For
example, an output of `{ "result": "success", "userId": 123 }` is incorrectly
transformed into `{ "userId": 123, "result": { "result": "success", "userId": 123 } }`.
This leads to silent data corruption, as the original value of the `result` property is
lost, which can break subsequent workflow steps that depend on it.

Did we get this right? 👍 / 👎 to inform future reviews.


return [key, result];
}),
);
};
Loading