Skip to content

Commit 4a17367

Browse files
committed
fix(logs): surface handled errors as info in logs
1 parent 8a24b56 commit 4a17367

File tree

11 files changed

+592
-20
lines changed

11 files changed

+592
-20
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-spans/trace-spans.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function parseTime(value?: string | number | null): number {
6767
}
6868

6969
/**
70-
* Checks if a span or any of its descendants has an error
70+
* Checks if a span or any of its descendants has an error (any error).
7171
*/
7272
function hasErrorInTree(span: TraceSpan): boolean {
7373
if (span.status === 'error') return true
@@ -80,6 +80,28 @@ function hasErrorInTree(span: TraceSpan): boolean {
8080
return false
8181
}
8282

83+
/**
84+
* Checks if a span or any of its descendants has an unhandled error.
85+
* Container spans (loop, iteration, parallel) are skipped — only leaf
86+
* block spans with errorHandled are considered. Used only for the root
87+
* workflow span to match the actual workflow status.
88+
*/
89+
const CONTAINER_TYPES = new Set(['loop', 'loop-iteration', 'parallel', 'parallel-iteration'])
90+
function hasUnhandledErrorInTree(span: TraceSpan): boolean {
91+
const type = span.type?.toLowerCase() || ''
92+
if (CONTAINER_TYPES.has(type)) {
93+
return span.children ? span.children.some(hasUnhandledErrorInTree) : false
94+
}
95+
if (span.status === 'error' && !span.errorHandled) return true
96+
if (span.children && span.children.length > 0) {
97+
return span.children.some((child) => hasUnhandledErrorInTree(child))
98+
}
99+
if (span.toolCalls && span.toolCalls.length > 0) {
100+
return span.toolCalls.some((tc) => tc.error)
101+
}
102+
return false
103+
}
104+
83105
/**
84106
* Normalizes and sorts trace spans recursively.
85107
* Deduplicates children and sorts by start time.
@@ -478,14 +500,13 @@ const TraceSpanNode = memo(function TraceSpanNode({
478500
const duration = span.duration || spanEndTime - spanStartTime
479501

480502
const isDirectError = span.status === 'error'
481-
const hasNestedError = hasErrorInTree(span)
503+
const isRootWorkflow = depth === 0
504+
const isRootWorkflowSpan = isRootWorkflow && span.type?.toLowerCase() === 'workflow'
505+
const hasNestedError = isRootWorkflowSpan ? hasUnhandledErrorInTree(span) : hasErrorInTree(span)
482506
const showErrorStyle = isDirectError || hasNestedError
483507

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

486-
// Root workflow execution is always expanded and has no toggle
487-
const isRootWorkflow = depth === 0
488-
489510
// Build all children including tool calls
490511
const allChildren = useMemo(() => {
491512
const children: TraceSpan[] = []

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ const WorkflowEdgeComponent = ({
9595
color = 'var(--brand-tertiary-2)'
9696
} else if (edgeRunStatus === 'success') {
9797
// Use green for preview mode, default for canvas execution
98-
// This also applies to error edges that were taken (error path executed)
9998
color = previewExecutionStatus ? 'var(--brand-tertiary-2)' : 'var(--border-success)'
10099
} else if (edgeRunStatus === 'error') {
101100
color = 'var(--text-error)'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ interface DebugValidationResult {
5555
interface BlockEventHandlerConfig {
5656
workflowId?: string
5757
executionId?: string
58-
workflowEdges: Array<{ id: string; target: string }>
58+
workflowEdges: Array<{ id: string; target: string; sourceHandle?: string | null }>
5959
activeBlocksSet: Set<string>
6060
accumulatedBlockLogs: BlockLog[]
6161
accumulatedBlockStates: Map<string, BlockState>
@@ -322,7 +322,8 @@ export function useWorkflowExecution() {
322322
if (!workflowId) return
323323
const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId)
324324
incomingEdges.forEach((edge) => {
325-
setEdgeRunStatus(workflowId, edge.id, 'success')
325+
const status = edge.sourceHandle === 'error' ? 'error' : 'success'
326+
setEdgeRunStatus(workflowId, edge.id, status)
326327
})
327328
}
328329

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ export class BlockExecutor {
288288

289289
const hasErrorPort = this.hasErrorPortEdge(node)
290290
if (hasErrorPort) {
291+
if (blockLog) {
292+
blockLog.errorHandled = true
293+
}
291294
logger.info('Block has error port - returning error output instead of throwing', {
292295
blockId: node.id,
293296
error: errorMessage,

apps/sim/executor/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export interface BlockLog {
112112
output?: any
113113
input?: any
114114
error?: string
115+
/** Whether this error was handled by an error handler path (error port) */
116+
errorHandled?: boolean
115117
loopId?: string
116118
parallelId?: string
117119
iterationIndex?: number

apps/sim/lib/logs/execution/logger.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,18 @@ export class ExecutionLogger implements IExecutionLoggerService {
219219
| { traceSpans?: TraceSpan[] }
220220
| undefined
221221

222-
// Determine if workflow failed by checking trace spans for errors
222+
// Determine if workflow failed by checking trace spans for unhandled errors
223+
// Errors handled by error handler paths (errorHandled: true) don't count as workflow failures
223224
// Use the override if provided (for cost-only fallback scenarios)
225+
const containerTypes = ['loop', 'loop-iteration', 'parallel', 'parallel-iteration']
224226
const hasErrors = traceSpans?.some((span: any) => {
225227
const checkSpanForErrors = (s: any): boolean => {
226-
if (s.status === 'error') return true
228+
if (containerTypes.includes(s.type?.toLowerCase() || '')) {
229+
return s.children && Array.isArray(s.children)
230+
? s.children.some(checkSpanForErrors)
231+
: false
232+
}
233+
if (s.status === 'error' && !s.errorHandled) return true
227234
if (s.children && Array.isArray(s.children)) {
228235
return s.children.some(checkSpanForErrors)
229236
}

apps/sim/lib/logs/execution/logging-session.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,15 @@ export class LoggingSession {
299299
'@/lib/core/telemetry'
300300
)
301301

302+
const containerTypes = ['loop', 'loop-iteration', 'parallel', 'parallel-iteration']
302303
const hasErrors = traceSpans.some((span: any) => {
303304
const checkForErrors = (s: any): boolean => {
304-
if (s.status === 'error') return true
305+
if (containerTypes.includes(s.type?.toLowerCase() || '')) {
306+
return s.children && Array.isArray(s.children)
307+
? s.children.some(checkForErrors)
308+
: false
309+
}
310+
if (s.status === 'error' && !s.errorHandled) return true
305311
if (s.children && Array.isArray(s.children)) {
306312
return s.children.some(checkForErrors)
307313
}

0 commit comments

Comments
 (0)