Skip to content

Commit 8bf2e69

Browse files
committed
fix(child-workflow): nested spans handoff
1 parent 12100e6 commit 8bf2e69

File tree

3 files changed

+63
-27
lines changed

3 files changed

+63
-27
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { TraceSpan } from '@/lib/logs/types'
2+
import type { ExecutionResult } from '@/executor/types'
3+
4+
interface ChildWorkflowErrorOptions {
5+
message: string
6+
childWorkflowName: string
7+
childTraceSpans?: TraceSpan[]
8+
executionResult?: ExecutionResult
9+
cause?: Error
10+
}
11+
12+
/**
13+
* Error raised when a child workflow execution fails.
14+
*/
15+
export class ChildWorkflowError extends Error {
16+
readonly childTraceSpans: TraceSpan[]
17+
readonly childWorkflowName: string
18+
readonly executionResult?: ExecutionResult
19+
20+
constructor(options: ChildWorkflowErrorOptions) {
21+
super(options.message, { cause: options.cause })
22+
this.name = 'ChildWorkflowError'
23+
this.childWorkflowName = options.childWorkflowName
24+
this.childTraceSpans = options.childTraceSpans ?? []
25+
this.executionResult = options.executionResult
26+
}
27+
28+
static isChildWorkflowError(error: unknown): error is ChildWorkflowError {
29+
return error instanceof ChildWorkflowError
30+
}
31+
}

apps/sim/executor/execution/block-executor.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isSentinelBlockType,
1414
} from '@/executor/constants'
1515
import type { DAGNode } from '@/executor/dag/builder'
16+
import { ChildWorkflowError } from '@/executor/errors/child-workflow-error'
1617
import type { BlockStateWriter, ContextExtensions } from '@/executor/execution/types'
1718
import {
1819
generatePauseContextId,
@@ -213,24 +214,28 @@ export class BlockExecutor {
213214
? resolvedInputs
214215
: ((block.config?.params as Record<string, any> | undefined) ?? {})
215216

216-
if (blockLog) {
217-
blockLog.endedAt = new Date().toISOString()
218-
blockLog.durationMs = duration
219-
blockLog.success = false
220-
blockLog.error = errorMessage
221-
blockLog.input = input
222-
}
223-
224217
const errorOutput: NormalizedBlockOutput = {
225218
error: errorMessage,
226219
}
227220

228-
if (error && typeof error === 'object' && 'childTraceSpans' in error) {
221+
if (ChildWorkflowError.isChildWorkflowError(error)) {
222+
errorOutput.childTraceSpans = error.childTraceSpans
223+
errorOutput.childWorkflowName = error.childWorkflowName
224+
} else if (error && typeof error === 'object' && 'childTraceSpans' in error) {
229225
errorOutput.childTraceSpans = (error as any).childTraceSpans
230226
}
231227

232228
this.state.setBlockOutput(node.id, errorOutput, duration)
233229

230+
if (blockLog) {
231+
blockLog.endedAt = new Date().toISOString()
232+
blockLog.durationMs = duration
233+
blockLog.success = false
234+
blockLog.error = errorMessage
235+
blockLog.input = input
236+
blockLog.output = this.filterOutputForLog(block, errorOutput)
237+
}
238+
234239
logger.error(
235240
phase === 'input_resolution' ? 'Failed to resolve block inputs' : 'Block execution failed',
236241
{

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

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { TraceSpan } from '@/lib/logs/types'
44
import type { BlockOutput } from '@/blocks/types'
55
import { Executor } from '@/executor'
66
import { BlockType, DEFAULTS, HTTP } from '@/executor/constants'
7+
import { ChildWorkflowError } from '@/executor/errors/child-workflow-error'
78
import type {
89
BlockHandler,
910
ExecutionContext,
@@ -145,31 +146,30 @@ export class WorkflowBlockHandler implements BlockHandler {
145146
const childWorkflowName = workflowMetadata?.name || workflowId
146147

147148
const originalError = error.message || 'Unknown error'
148-
const wrappedError = new Error(
149-
`Error in child workflow "${childWorkflowName}": ${originalError}`
150-
)
151-
149+
let childTraceSpans: WorkflowTraceSpan[] = []
150+
let executionResult: ExecutionResult | undefined
152151
if (error.executionResult?.logs) {
153-
const executionResult = error.executionResult as ExecutionResult
152+
executionResult = error.executionResult as ExecutionResult
154153

155154
logger.info(`Extracting child trace spans from error.executionResult`, {
156155
hasLogs: (executionResult.logs?.length ?? 0) > 0,
157156
logCount: executionResult.logs?.length ?? 0,
158157
})
159158

160-
const childTraceSpans = this.captureChildWorkflowLogs(
161-
executionResult,
162-
childWorkflowName,
163-
ctx
164-
)
159+
childTraceSpans = this.captureChildWorkflowLogs(executionResult, childWorkflowName, ctx)
165160

166161
logger.info(`Captured ${childTraceSpans.length} child trace spans from failed execution`)
167-
;(wrappedError as any).childTraceSpans = childTraceSpans
168162
} else if (error.childTraceSpans && Array.isArray(error.childTraceSpans)) {
169-
;(wrappedError as any).childTraceSpans = error.childTraceSpans
163+
childTraceSpans = error.childTraceSpans
170164
}
171165

172-
throw wrappedError
166+
throw new ChildWorkflowError({
167+
message: `Error in child workflow "${childWorkflowName}": ${originalError}`,
168+
childWorkflowName,
169+
childTraceSpans,
170+
executionResult,
171+
cause: error,
172+
})
173173
}
174174
}
175175

@@ -441,11 +441,11 @@ export class WorkflowBlockHandler implements BlockHandler {
441441

442442
if (!success) {
443443
logger.warn(`Child workflow ${childWorkflowName} failed`)
444-
const error = new Error(
445-
`Error in child workflow "${childWorkflowName}": ${childResult.error || 'Child workflow execution failed'}`
446-
)
447-
;(error as any).childTraceSpans = childTraceSpans || []
448-
throw error
444+
throw new ChildWorkflowError({
445+
message: `Error in child workflow "${childWorkflowName}": ${childResult.error || 'Child workflow execution failed'}`,
446+
childWorkflowName,
447+
childTraceSpans: childTraceSpans || [],
448+
})
449449
}
450450

451451
return {

0 commit comments

Comments
 (0)