@@ -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 ( / ^ W o r k f l o w c h a i n : ( .+ ?) \| ( .+ ) $ / )
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 * f a i l e d : \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