Skip to content

Commit cd3533e

Browse files
committed
fix error message formatting
1 parent b909af8 commit cd3533e

File tree

2 files changed

+82
-14
lines changed

2 files changed

+82
-14
lines changed

apps/sim/executor/handlers/workflow/workflow-handler.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ describe('WorkflowBlockHandler', () => {
118118
}
119119

120120
await expect(handler.execute(deepContext, mockBlock, inputs)).rejects.toThrow(
121-
'Error in child workflow "child-workflow-id": Maximum workflow nesting depth of 10 exceeded'
121+
'"child-workflow-id" failed: Maximum workflow nesting depth of 10 exceeded'
122122
)
123123
})
124124

@@ -132,7 +132,7 @@ describe('WorkflowBlockHandler', () => {
132132
})
133133

134134
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
135-
'Error in child workflow "non-existent-workflow": Child workflow non-existent-workflow not found'
135+
'"non-existent-workflow" failed: Child workflow non-existent-workflow not found'
136136
)
137137
})
138138

@@ -142,7 +142,7 @@ describe('WorkflowBlockHandler', () => {
142142
mockFetch.mockRejectedValueOnce(new Error('Network error'))
143143

144144
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
145-
'Error in child workflow "child-workflow-id": Network error'
145+
'"child-workflow-id" failed: Network error'
146146
)
147147
})
148148
})
@@ -212,7 +212,7 @@ describe('WorkflowBlockHandler', () => {
212212

213213
expect(() =>
214214
(handler as any).mapChildOutputToParent(childResult, 'child-id', 'Child Workflow', 100)
215-
).toThrow('Error in child workflow "Child Workflow": Child workflow failed')
215+
).toThrow('"Child Workflow" failed: Child workflow failed')
216216

217217
try {
218218
;(handler as any).mapChildOutputToParent(childResult, 'child-id', 'Child Workflow', 100)

apps/sim/executor/handlers/workflow/workflow-handler.ts

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export class WorkflowBlockHandler implements BlockHandler {
5252
throw new Error('No workflow selected for execution')
5353
}
5454

55+
// Initialize with registry name, will be updated with loaded workflow name
56+
const { workflows } = useWorkflowRegistry.getState()
57+
const workflowMetadata = workflows[workflowId]
58+
let childWorkflowName = workflowMetadata?.name || workflowId
59+
5560
try {
5661
const currentDepth = (ctx.workflowId?.split('_sub_').length || 1) - 1
5762
if (currentDepth >= DEFAULTS.MAX_WORKFLOW_DEPTH) {
@@ -75,9 +80,8 @@ export class WorkflowBlockHandler implements BlockHandler {
7580
throw new Error(`Child workflow ${workflowId} not found`)
7681
}
7782

78-
const { workflows } = useWorkflowRegistry.getState()
79-
const workflowMetadata = workflows[workflowId]
80-
const childWorkflowName = workflowMetadata?.name || childWorkflow.name || 'Unknown Workflow'
83+
// Update with loaded workflow name (more reliable than registry)
84+
childWorkflowName = workflowMetadata?.name || childWorkflow.name || 'Unknown Workflow'
8185

8286
logger.info(
8387
`Executing child workflow: ${childWorkflowName} (${workflowId}) at depth ${currentDepth}`
@@ -142,11 +146,6 @@ export class WorkflowBlockHandler implements BlockHandler {
142146
} catch (error: unknown) {
143147
logger.error(`Error executing child workflow ${workflowId}:`, error)
144148

145-
const { workflows } = useWorkflowRegistry.getState()
146-
const workflowMetadata = workflows[workflowId]
147-
const childWorkflowName = workflowMetadata?.name || workflowId
148-
149-
const originalError = error instanceof Error ? error.message : 'Unknown error'
150149
let childTraceSpans: WorkflowTraceSpan[] = []
151150
let executionResult: ExecutionResult | undefined
152151

@@ -165,8 +164,11 @@ export class WorkflowBlockHandler implements BlockHandler {
165164
childTraceSpans = error.childTraceSpans
166165
}
167166

167+
// Build a cleaner error message for nested workflow errors
168+
const errorMessage = this.buildNestedWorkflowErrorMessage(childWorkflowName, error)
169+
168170
throw new ChildWorkflowError({
169-
message: `Error in child workflow "${childWorkflowName}": ${originalError}`,
171+
message: errorMessage,
170172
childWorkflowName,
171173
childTraceSpans,
172174
executionResult,
@@ -175,6 +177,72 @@ export class WorkflowBlockHandler implements BlockHandler {
175177
}
176178
}
177179

180+
/**
181+
* Builds a cleaner error message for nested workflow errors.
182+
* Parses nested error messages to extract workflow chain and root error.
183+
*/
184+
private buildNestedWorkflowErrorMessage(childWorkflowName: string, error: unknown): string {
185+
const originalError = error instanceof Error ? error.message : 'Unknown error'
186+
187+
// Extract any nested workflow names from the error message
188+
const { chain, rootError } = this.parseNestedWorkflowError(originalError)
189+
190+
// Add current workflow to the beginning of the chain
191+
chain.unshift(childWorkflowName)
192+
193+
// If we have a chain (nested workflows), format nicely
194+
if (chain.length > 1) {
195+
return `Workflow chain: ${chain.join(' → ')} | ${rootError}`
196+
}
197+
198+
// Single workflow failure
199+
return `"${childWorkflowName}" failed: ${rootError}`
200+
}
201+
202+
/**
203+
* Parses a potentially nested workflow error message to extract:
204+
* - The chain of workflow names
205+
* - The actual root error message (preserving the block prefix for the failing block)
206+
*
207+
* Handles formats like:
208+
* - "workflow-name" failed: error
209+
* - [block_type] Block Name: "workflow-name" failed: error
210+
* - Workflow chain: A → B | error
211+
*/
212+
private parseNestedWorkflowError(message: string): { chain: string[]; rootError: string } {
213+
const chain: string[] = []
214+
const remaining = message
215+
216+
// First, check if it's already in chain format
217+
const chainMatch = remaining.match(/^Workflow chain: (.+?) \| (.+)$/)
218+
if (chainMatch) {
219+
const chainPart = chainMatch[1]
220+
const errorPart = chainMatch[2]
221+
chain.push(...chainPart.split(' → ').map((s) => s.trim()))
222+
return { chain, rootError: errorPart }
223+
}
224+
225+
// Extract workflow names from patterns like:
226+
// - "workflow-name" failed:
227+
// - [block_type] Block Name: "workflow-name" failed:
228+
const workflowPattern = /(?:\[[^\]]+\]\s*[^:]+:\s*)?"([^"]+)"\s*failed:\s*/g
229+
let match: RegExpExecArray | null
230+
let lastIndex = 0
231+
232+
match = workflowPattern.exec(remaining)
233+
while (match !== null) {
234+
chain.push(match[1])
235+
lastIndex = match.index + match[0].length
236+
match = workflowPattern.exec(remaining)
237+
}
238+
239+
// The root error is everything after the last match
240+
// Keep the block prefix (e.g., [function] Function 1:) so we know which block failed
241+
const rootError = lastIndex > 0 ? remaining.slice(lastIndex) : remaining
242+
243+
return { chain, rootError: rootError.trim() || 'Unknown error' }
244+
}
245+
178246
private async loadChildWorkflow(workflowId: string) {
179247
const headers = await buildAuthHeaders()
180248
const url = buildAPIUrl(`/api/workflows/${workflowId}`)
@@ -444,7 +512,7 @@ export class WorkflowBlockHandler implements BlockHandler {
444512
if (!success) {
445513
logger.warn(`Child workflow ${childWorkflowName} failed`)
446514
throw new ChildWorkflowError({
447-
message: `Error in child workflow "${childWorkflowName}": ${childResult.error || 'Child workflow execution failed'}`,
515+
message: `"${childWorkflowName}" failed: ${childResult.error || 'Child workflow execution failed'}`,
448516
childWorkflowName,
449517
childTraceSpans: childTraceSpans || [],
450518
})

0 commit comments

Comments
 (0)