Skip to content
Merged
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 @@ -67,7 +67,7 @@ function parseTime(value?: string | number | null): number {
}

/**
* Checks if a span or any of its descendants has an error
* Checks if a span or any of its descendants has an error (any error).
*/
function hasErrorInTree(span: TraceSpan): boolean {
if (span.status === 'error') return true
Expand All @@ -80,6 +80,23 @@ function hasErrorInTree(span: TraceSpan): boolean {
return false
}

/**
* Checks if a span or any of its descendants has an unhandled error.
* Spans with errorHandled: true (including containers that propagate it)
* are skipped. Used only for the root workflow span to match the actual
* workflow status.
*/
function hasUnhandledErrorInTree(span: TraceSpan): boolean {
if (span.status === 'error' && !span.errorHandled) return true
if (span.children && span.children.length > 0) {
return span.children.some((child) => hasUnhandledErrorInTree(child))
}
if (span.toolCalls && span.toolCalls.length > 0 && !span.errorHandled) {
return span.toolCalls.some((tc) => tc.error)
}
return false
}

/**
* Normalizes and sorts trace spans recursively.
* Deduplicates children and sorts by start time.
Expand Down Expand Up @@ -478,14 +495,13 @@ const TraceSpanNode = memo(function TraceSpanNode({
const duration = span.duration || spanEndTime - spanStartTime

const isDirectError = span.status === 'error'
const hasNestedError = hasErrorInTree(span)
const isRootWorkflow = depth === 0
const isRootWorkflowSpan = isRootWorkflow && span.type?.toLowerCase() === 'workflow'
const hasNestedError = isRootWorkflowSpan ? hasUnhandledErrorInTree(span) : hasErrorInTree(span)
const showErrorStyle = isDirectError || hasNestedError

const { icon: BlockIcon, bgColor } = getBlockIconAndColor(span.type, span.name)

// Root workflow execution is always expanded and has no toggle
const isRootWorkflow = depth === 0

// Build all children including tool calls
const allChildren = useMemo(() => {
const children: TraceSpan[] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ const WorkflowEdgeComponent = ({
color = 'var(--brand-tertiary-2)'
} else if (edgeRunStatus === 'success') {
// Use green for preview mode, default for canvas execution
// This also applies to error edges that were taken (error path executed)
color = previewExecutionStatus ? 'var(--brand-tertiary-2)' : 'var(--border-success)'
} else if (edgeRunStatus === 'error') {
color = 'var(--text-error)'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface DebugValidationResult {
interface BlockEventHandlerConfig {
workflowId?: string
executionId?: string
workflowEdges: Array<{ id: string; target: string }>
workflowEdges: Array<{ id: string; target: string; sourceHandle?: string | null }>
activeBlocksSet: Set<string>
accumulatedBlockLogs: BlockLog[]
accumulatedBlockStates: Map<string, BlockState>
Expand Down Expand Up @@ -322,7 +322,8 @@ export function useWorkflowExecution() {
if (!workflowId) return
const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId)
incomingEdges.forEach((edge) => {
setEdgeRunStatus(workflowId, edge.id, 'success')
const status = edge.sourceHandle === 'error' ? 'error' : 'success'
setEdgeRunStatus(workflowId, edge.id, status)
})
}

Expand Down
3 changes: 3 additions & 0 deletions apps/sim/executor/execution/block-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ export class BlockExecutor {

const hasErrorPort = this.hasErrorPortEdge(node)
if (hasErrorPort) {
if (blockLog) {
blockLog.errorHandled = true
}
logger.info('Block has error port - returning error output instead of throwing', {
blockId: node.id,
error: errorMessage,
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/executor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export interface BlockLog {
output?: any
input?: any
error?: string
/** Whether this error was handled by an error handler path (error port) */
errorHandled?: boolean
loopId?: string
parallelId?: string
iterationIndex?: number
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/lib/logs/execution/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,12 @@ export class ExecutionLogger implements IExecutionLoggerService {
| { traceSpans?: TraceSpan[] }
| undefined

// Determine if workflow failed by checking trace spans for errors
// Determine if workflow failed by checking trace spans for unhandled errors
// Errors handled by error handler paths (errorHandled: true) don't count as workflow failures
// Use the override if provided (for cost-only fallback scenarios)
const hasErrors = traceSpans?.some((span: any) => {
const checkSpanForErrors = (s: any): boolean => {
if (s.status === 'error') return true
if (s.status === 'error' && !s.errorHandled) return true
if (s.children && Array.isArray(s.children)) {
return s.children.some(checkSpanForErrors)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/logs/execution/logging-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export class LoggingSession {

const hasErrors = traceSpans.some((span: any) => {
const checkForErrors = (s: any): boolean => {
if (s.status === 'error') return true
if (s.status === 'error' && !s.errorHandled) return true
if (s.children && Array.isArray(s.children)) {
return s.children.some(checkForErrors)
}
Expand Down
Loading