Skip to content

Commit a8504f7

Browse files
waleedlatif1claude
andcommitted
fix(executor): handle Response block format in workflow executor tool
When a child workflow has a Response block, the execute endpoint returns the block's data directly without the standard {success, executionId, output} wrapper. The transformResponse was defaulting success to false, causing "Tool execution failed" errors even when the workflow completed successfully. Detect the response format by checking for executionId + success boolean, and fall back to response.ok for Response block responses. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 68d207d commit a8504f7

File tree

2 files changed

+110
-5
lines changed

2 files changed

+110
-5
lines changed

apps/sim/tools/workflow/executor.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,102 @@ describe('workflowExecutorTool', () => {
242242
})
243243
})
244244

245+
describe('transformResponse', () => {
246+
const transformResponse = workflowExecutorTool.transformResponse!
247+
248+
function mockResponse(body: any, status = 200): Response {
249+
return {
250+
ok: status >= 200 && status < 300,
251+
status,
252+
json: async () => body,
253+
} as unknown as Response
254+
}
255+
256+
it.concurrent('should parse standard format response', async () => {
257+
const body = {
258+
success: true,
259+
executionId: 'exec-123',
260+
output: { result: 'hello' },
261+
metadata: { duration: 500 },
262+
}
263+
264+
const result = await transformResponse(mockResponse(body))
265+
266+
expect(result.success).toBe(true)
267+
expect(result.output).toEqual({ result: 'hello' })
268+
expect(result.duration).toBe(500)
269+
expect(result.error).toBeUndefined()
270+
})
271+
272+
it.concurrent('should parse standard format failure', async () => {
273+
const body = {
274+
success: false,
275+
executionId: 'exec-123',
276+
output: {},
277+
error: 'Something went wrong',
278+
}
279+
280+
const result = await transformResponse(mockResponse(body))
281+
282+
expect(result.success).toBe(false)
283+
expect(result.error).toBe('Something went wrong')
284+
})
285+
286+
it.concurrent(
287+
'should handle Response block format (no success/executionId wrapper)',
288+
async () => {
289+
const body = { issues: [], total: 0 }
290+
291+
const result = await transformResponse(mockResponse(body))
292+
293+
expect(result.success).toBe(true)
294+
expect(result.output).toEqual({ issues: [], total: 0 })
295+
expect(result.error).toBeUndefined()
296+
}
297+
)
298+
299+
it.concurrent(
300+
'should not misidentify user data containing success field as standard format',
301+
async () => {
302+
const body = { success: true, data: [1, 2, 3] }
303+
304+
const result = await transformResponse(mockResponse(body))
305+
306+
// No executionId → treated as Response block format
307+
expect(result.success).toBe(true)
308+
expect(result.output).toEqual({ success: true, data: [1, 2, 3] })
309+
}
310+
)
311+
312+
it.concurrent('should handle empty Response block data', async () => {
313+
const body = {}
314+
315+
const result = await transformResponse(mockResponse(body))
316+
317+
expect(result.success).toBe(true)
318+
expect(result.output).toEqual({})
319+
})
320+
321+
it.concurrent('should handle array Response block data', async () => {
322+
const body = [1, 2, 3]
323+
324+
const result = await transformResponse(mockResponse(body))
325+
326+
expect(result.success).toBe(true)
327+
expect(result.output).toEqual([1, 2, 3])
328+
})
329+
330+
it.concurrent('should preserve error field from Response block data', async () => {
331+
const body = { results: [], error: 'No results found' }
332+
333+
const result = await transformResponse(mockResponse(body))
334+
335+
expect(result.success).toBe(true)
336+
expect(result.output).toEqual({ results: [], error: 'No results found' })
337+
expect(result.error).toBe('No results found')
338+
})
339+
})
340+
245341
describe('tool metadata', () => {
246342
it.concurrent('should have correct id', () => {
247343
expect(workflowExecutorTool.id).toBe('workflow_executor')

apps/sim/tools/workflow/executor.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,24 @@ export const workflowExecutorTool: ToolConfig<
5353
},
5454
transformResponse: async (response: Response) => {
5555
const data = await response.json()
56-
const outputData = data?.output ?? {}
56+
57+
// The execute endpoint has two response shapes:
58+
// 1. Standard: { success, executionId, output, error, metadata }
59+
// 2. Response block: arbitrary user-defined data (no wrapper)
60+
// Detect standard format by checking for executionId (always present) + success boolean.
61+
const isStandardFormat =
62+
typeof data?.success === 'boolean' && typeof data?.executionId === 'string'
63+
64+
const outputData = isStandardFormat ? (data.output ?? {}) : data
65+
const success = isStandardFormat ? data.success : response.ok
5766

5867
return {
59-
success: data?.success ?? false,
60-
duration: data?.metadata?.duration ?? 0,
68+
success,
69+
duration: isStandardFormat ? (data?.metadata?.duration ?? 0) : 0,
6170
childWorkflowId: data?.workflowId ?? '',
6271
childWorkflowName: data?.workflowName ?? '',
63-
output: outputData, // For OpenAI provider
64-
result: outputData, // For backwards compatibility
72+
output: outputData,
73+
result: outputData,
6574
error: data?.error,
6675
}
6776
},

0 commit comments

Comments
 (0)