diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx
index 5670cb048e4..de6533fa482 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx
@@ -701,7 +701,8 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
if (span.iterationIndex !== undefined)
metaEntries.push({ label: 'Iteration', value: String(span.iterationIndex + 1) })
- const statusLabel = hasError ? 'Error' : 'Success'
+ const statusLabel =
+ isDirectError && span.errorHandled ? 'Handled error' : hasError ? 'Error' : 'Success'
return (
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts
index b3dc95416bb..d9c79bb0152 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts
+++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts
@@ -38,7 +38,9 @@ export function isIterationType(type: string): boolean {
export function hasErrorInTree(span: TraceSpan): boolean {
if (span.status === 'error') return true
- if (span.children?.length) return span.children.some(hasErrorInTree)
+ if (span.children?.length) {
+ return span.children.some((child) => hasUnhandledError(child, { includeToolCalls: true }))
+ }
if (span.toolCalls?.length) return span.toolCalls.some((tc) => tc.error)
return false
}
diff --git a/apps/sim/executor/types.ts b/apps/sim/executor/types.ts
index 109d6e31e71..1a1788c59eb 100644
--- a/apps/sim/executor/types.ts
+++ b/apps/sim/executor/types.ts
@@ -183,6 +183,8 @@ export interface BlockTokens {
/** A single tool invocation recorded by an agent-type block. */
export interface BlockToolCall {
name: string
+ success?: boolean
+ status?: string
duration?: number
startTime?: string
endTime?: string
diff --git a/apps/sim/lib/logs/execution/trace-spans/span-factory.ts b/apps/sim/lib/logs/execution/trace-spans/span-factory.ts
index 5ca4d8d87a5..d2e657deea3 100644
--- a/apps/sim/lib/logs/execution/trace-spans/span-factory.ts
+++ b/apps/sim/lib/logs/execution/trace-spans/span-factory.ts
@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { isRecordLike } from '@sim/utils/object'
+import { truncate } from '@sim/utils/string'
import type { ProviderTiming, TraceSpan } from '@/lib/logs/types'
import {
isConditionBlockType,
@@ -14,6 +15,7 @@ import type {
} from '@/executor/types'
const logger = createLogger('SpanFactory')
+const TOOL_CALL_ERROR_MAX_LENGTH = 4096
/** A BlockLog that has already passed the id/type validity check. */
type ValidBlockLog = BlockLog & { blockType: string }
@@ -24,6 +26,49 @@ function normalizeTraceOutput(value: unknown): Record | undefin
return isRecordLike(value) ? value : { value }
}
+function nonEmptyString(value: unknown): string | undefined {
+ return typeof value === 'string' && value.trim().length > 0 ? value : undefined
+}
+
+/**
+ * Returns the canonical error message for a failed agent tool call.
+ *
+ * Providers expose failures through several normalized shapes. A nested
+ * `success: false` is explicit; the generic nested fallback requires Sim's
+ * complete error envelope so successful tool data with an `error` field is
+ * not misclassified.
+ */
+function getToolCallErrorMessage(
+ toolCall: BlockToolCall | undefined,
+ segmentErrorMessage?: string
+): string | undefined {
+ const rawResult = toolCall?.result ?? toolCall?.output
+ const result = isRecordLike(rawResult) ? rawResult : undefined
+ const topLevelError = nonEmptyString(toolCall?.error)
+ const segmentError = nonEmptyString(segmentErrorMessage)
+ const hasExplicitFailure =
+ toolCall?.success === false ||
+ toolCall?.status === 'error' ||
+ result?.success === false ||
+ topLevelError !== undefined ||
+ segmentError !== undefined
+ const hasStandardSimError =
+ result?.error === true &&
+ nonEmptyString(result.message) !== undefined &&
+ nonEmptyString(result.tool) !== undefined
+
+ if (!hasExplicitFailure && !hasStandardSimError) return undefined
+
+ const nestedMessage = nonEmptyString(result?.message) ?? nonEmptyString(result?.error)
+ const message =
+ topLevelError ??
+ segmentError ??
+ nestedMessage ??
+ `Tool ${toolCall?.name || 'call'} execution failed`
+
+ return truncate(message, TOOL_CALL_ERROR_MAX_LENGTH)
+}
+
/**
* Creates a TraceSpan from a BlockLog. Returns null for invalid logs.
*
@@ -202,6 +247,10 @@ function buildChildrenFromTimeSegments(
const match = callsForName[currentIndex]
toolCallIndices.set(normalizedName, currentIndex + 1)
const output = normalizeTraceOutput(match?.result ?? match?.output)
+ const errorMessage = getToolCallErrorMessage(match, segment.errorMessage)
+ const errorHandled = Boolean(
+ errorMessage && span.type === 'agent' && span.status === 'success'
+ )
const toolChild: TraceSpan = {
id: `${span.id}-segment-${index}`,
@@ -210,13 +259,14 @@ function buildChildrenFromTimeSegments(
duration: segment.duration,
startTime: segmentStartTime,
endTime: segmentEndTime,
- status: match?.error || segment.errorMessage ? 'error' : 'success',
+ status: errorMessage ? 'error' : 'success',
input: match?.arguments ?? match?.input,
output: match?.error ? { error: match.error, ...output } : output,
+ ...(errorHandled && { errorHandled: true }),
}
if (segment.toolCallId) toolChild.toolCallId = segment.toolCallId
if (segment.errorType) toolChild.errorType = segment.errorType
- if (segment.errorMessage) toolChild.errorMessage = segment.errorMessage
+ if (errorMessage) toolChild.errorMessage = errorMessage
return toolChild
}
@@ -280,6 +330,8 @@ function buildChildrenFromToolCalls(span: TraceSpan, log: ValidBlockLog): TraceS
const startTime = tc.startTime ?? log.startedAt
const endTime = tc.endTime ?? log.endedAt
const output = normalizeTraceOutput(tc.result ?? tc.output)
+ const errorMessage = getToolCallErrorMessage(tc)
+ const errorHandled = Boolean(errorMessage && span.type === 'agent' && span.status === 'success')
return {
id: `${span.id}-tool-${index}`,
name: stripCustomToolPrefix(tc.name ?? 'unnamed-tool'),
@@ -287,9 +339,11 @@ function buildChildrenFromToolCalls(span: TraceSpan, log: ValidBlockLog): TraceS
duration: tc.duration ?? 0,
startTime,
endTime,
- status: tc.error ? 'error' : 'success',
+ status: errorMessage ? 'error' : 'success',
input: tc.arguments ?? tc.input,
output: tc.error ? { error: tc.error, ...output } : output,
+ ...(errorMessage && { errorMessage }),
+ ...(errorHandled && { errorHandled: true }),
}
})
}
diff --git a/apps/sim/lib/logs/execution/trace-spans/trace-spans.test.ts b/apps/sim/lib/logs/execution/trace-spans/trace-spans.test.ts
index 77b333bbe75..216a847e9cc 100644
--- a/apps/sim/lib/logs/execution/trace-spans/trace-spans.test.ts
+++ b/apps/sim/lib/logs/execution/trace-spans/trace-spans.test.ts
@@ -341,10 +341,27 @@ describe('buildTraceSpans', () => {
expect(toolCall.output).toEqual({ analysis: 'completed' })
})
- it.concurrent('handles tool calls with errors in timeSegments', () => {
+ it.concurrent.each([
+ {
+ toolResult: {
+ error: true,
+ message: 'Tool execution failed',
+ tool: 'custom_failing_tool',
+ },
+ expectedMessage: 'Tool execution failed',
+ },
+ {
+ toolResult: {
+ success: false,
+ error: 'MCP server connection failed',
+ },
+ expectedMessage: 'MCP server connection failed',
+ },
+ ])('handles tool calls with errors in timeSegments', ({ toolResult, expectedMessage }) => {
const mockExecutionResult: ExecutionResult = {
success: true,
output: { content: 'Final output' },
+ metadata: { duration: 3000, startTime: '2024-01-01T10:00:00.000Z' },
logs: [
{
blockId: 'agent-4',
@@ -391,7 +408,7 @@ describe('buildTraceSpans', () => {
{
name: 'failing_tool',
arguments: { input: 'test' },
- error: 'Tool execution failed',
+ result: toolResult,
duration: 1000,
startTime: '2024-01-01T10:00:01.000Z',
endTime: '2024-01-01T10:00:02.000Z',
@@ -407,7 +424,10 @@ describe('buildTraceSpans', () => {
const { traceSpans } = buildTraceSpans(mockExecutionResult)
expect(traceSpans).toHaveLength(1)
- const agentSpan = traceSpans[0]
+ const workflowSpan = traceSpans[0]
+ expect(workflowSpan.status).toBe('success')
+ const agentSpan = workflowSpan.children![0]
+ expect(agentSpan.status).toBe('success')
expect(agentSpan.children).toBeDefined()
expect(agentSpan.children).toHaveLength(3)
@@ -416,8 +436,10 @@ describe('buildTraceSpans', () => {
expect(toolSegment.name).toBe('failing_tool')
expect(toolSegment.type).toBe('tool')
expect(toolSegment.status).toBe('error')
+ expect(toolSegment.errorHandled).toBe(true)
+ expect(toolSegment.errorMessage).toBe(expectedMessage)
expect(toolSegment.input).toEqual({ input: 'test' })
- expect(toolSegment.output).toEqual({ error: 'Tool execution failed' })
+ expect(toolSegment.output).toEqual(toolResult)
})
it.concurrent('handles blocks without tool calls', () => {
diff --git a/apps/sim/lib/workspace-events/constants.ts b/apps/sim/lib/workspace-events/constants.ts
index 12f855dc7f0..87406df585a 100644
--- a/apps/sim/lib/workspace-events/constants.ts
+++ b/apps/sim/lib/workspace-events/constants.ts
@@ -16,6 +16,7 @@ export const SIM_WORKSPACE_EVENT_TRIGGER_ID = 'sim_workspace_event'
export const SIM_PLAIN_EVENT_TYPES = [
'execution_success',
'execution_error',
+ 'agent_tool_error',
'workflow_deployed',
'workflow_undeployed',
] as const
@@ -89,7 +90,10 @@ interface SimEventPayloadField {
/** Restricts which event types surface this field in the tag dropdown. */
condition?: SimEventPayloadFieldCondition
/** Nested fields for json outputs, surfaced as dotted paths in the tag dropdown. */
- properties?: Record
+ properties?: Record<
+ string,
+ { type: 'string' | 'number' | 'json' | 'boolean'; description: string }
+ >
}
/** Run summary fields shared by top-level plain events and the nested triggeringRun. */
@@ -140,7 +144,10 @@ export const SIM_EVENT_PAYLOAD_FIELDS = {
},
runId: {
...RUN_SUMMARY_FIELDS.runId,
- condition: { field: 'eventType', value: [...SIM_PLAIN_RUN_EVENT_TYPES] },
+ condition: {
+ field: 'eventType',
+ value: [...SIM_PLAIN_RUN_EVENT_TYPES, 'agent_tool_error'],
+ },
},
durationMs: {
...RUN_SUMMARY_FIELDS.durationMs,
@@ -160,6 +167,41 @@ export const SIM_EVENT_PAYLOAD_FIELDS = {
condition: { field: 'eventType', value: [...SIM_RUN_BACKED_RULE_EVENT_TYPES] },
properties: RUN_SUMMARY_FIELDS,
},
+ toolError: {
+ type: 'json',
+ description: 'The failed Agent tool invocation',
+ condition: { field: 'eventType', value: 'agent_tool_error' },
+ properties: {
+ agentBlockId: {
+ type: 'string',
+ description: 'The Agent block ID',
+ },
+ agentBlockName: {
+ type: 'string',
+ description: 'The Agent block name',
+ },
+ toolName: {
+ type: 'string',
+ description: 'The failed tool name',
+ },
+ toolCallId: {
+ type: 'string',
+ description: 'The provider tool call ID, when available',
+ },
+ errorMessage: {
+ type: 'string',
+ description: 'The bounded tool failure message',
+ },
+ durationMs: {
+ type: 'number',
+ description: 'Tool invocation duration in milliseconds',
+ },
+ recovered: {
+ type: 'boolean',
+ description: 'Whether the Agent completed successfully after the failure',
+ },
+ },
+ },
version: {
type: 'number',
description: 'The deployment version number that was activated',
diff --git a/apps/sim/lib/workspace-events/emitter.test.ts b/apps/sim/lib/workspace-events/emitter.test.ts
index 3072ece6ac7..9e54202416c 100644
--- a/apps/sim/lib/workspace-events/emitter.test.ts
+++ b/apps/sim/lib/workspace-events/emitter.test.ts
@@ -46,7 +46,7 @@ vi.mock('@/lib/webhooks/processor', () => ({
processPolledWebhookEvent: mockProcessPolledWebhookEvent,
}))
-import type { WorkflowExecutionLog } from '@/lib/logs/types'
+import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
import {
emitExecutionCompletedEvent,
emitWorkflowDeployedEvent,
@@ -112,6 +112,45 @@ function makeLog(overrides: Partial = {}): WorkflowExecuti
}
}
+function makeAgentTrace(toolStatus: 'success' | 'error'): TraceSpan[] {
+ const toolSpan: TraceSpan = {
+ id: 'agent-1-segment-1',
+ name: 'always_fail',
+ type: 'tool',
+ duration: 12361,
+ startTime: '2026-08-14T00:08:49.018Z',
+ endTime: '2026-08-14T00:09:01.379Z',
+ status: toolStatus,
+ toolCallId: 'tool-call-1',
+ ...(toolStatus === 'error'
+ ? { errorHandled: true, errorMessage: 'Intentional test tool failure' }
+ : {}),
+ }
+ const agentSpan: TraceSpan = {
+ id: 'agent-1-span',
+ blockId: 'agent-1',
+ name: 'Agent',
+ type: 'agent',
+ duration: 15984,
+ startTime: '2026-08-14T00:08:46.273Z',
+ endTime: '2026-08-14T00:09:02.257Z',
+ status: 'success',
+ children: [toolSpan],
+ }
+ return [
+ {
+ id: 'workflow-execution',
+ name: 'Workflow Execution',
+ type: 'workflow',
+ duration: 15992,
+ startTime: '2026-08-14T00:08:46.265Z',
+ endTime: '2026-08-14T00:09:02.257Z',
+ status: 'success',
+ children: [agentSpan],
+ },
+ ]
+}
+
describe('emitExecutionCompletedEvent', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -189,6 +228,58 @@ describe('emitExecutionCompletedEvent', () => {
})
})
+ it('fires one agent_tool_error event alongside execution_success for a recovered failure', async () => {
+ const toolErrorSub = makeSubscription(makeConfig({ eventType: 'agent_tool_error' }), {
+ subscriberWorkflowId: 'wf-tool-error-sub',
+ })
+ const successSub = makeSubscription(makeConfig({ eventType: 'execution_success' }), {
+ subscriberWorkflowId: 'wf-success-sub',
+ })
+ mockFetchSubscriptions.mockResolvedValueOnce([toolErrorSub, successSub])
+
+ await emitExecutionCompletedEvent(
+ makeLog({
+ level: 'info',
+ executionData: {
+ finalOutput: { content: 'I recovered from the tool failure.' },
+ traceSpans: makeAgentTrace('error'),
+ },
+ })
+ )
+
+ expect(mockProcessPolledWebhookEvent).toHaveBeenCalledTimes(2)
+ expect(mockProcessPolledWebhookEvent.mock.calls[0][2]).toMatchObject({
+ event: 'agent_tool_error',
+ workflowId: 'wf-source',
+ workflowName: 'Source Workflow',
+ runId: 'exec-1',
+ toolError: {
+ agentBlockId: 'agent-1',
+ agentBlockName: 'Agent',
+ toolName: 'always_fail',
+ toolCallId: 'tool-call-1',
+ errorMessage: 'Intentional test tool failure',
+ durationMs: 12361,
+ recovered: true,
+ },
+ })
+ expect(mockProcessPolledWebhookEvent.mock.calls[1][2]).toMatchObject({
+ event: 'execution_success',
+ runId: 'exec-1',
+ })
+ })
+
+ it('does not fire agent_tool_error when every Agent tool call succeeded', async () => {
+ const toolErrorSub = makeSubscription(makeConfig({ eventType: 'agent_tool_error' }))
+ mockFetchSubscriptions.mockResolvedValueOnce([toolErrorSub])
+
+ await emitExecutionCompletedEvent(
+ makeLog({ executionData: { traceSpans: makeAgentTrace('success') } })
+ )
+
+ expect(mockProcessPolledWebhookEvent).not.toHaveBeenCalled()
+ })
+
it('respects the workflow scope filter, ignoring stale workflow ids', async () => {
const matching = makeSubscription(makeConfig({ workflowIds: ['wf-source', 'wf-deleted'] }), {
subscriberWorkflowId: 'wf-a',
diff --git a/apps/sim/lib/workspace-events/emitter.ts b/apps/sim/lib/workspace-events/emitter.ts
index ce672aef57a..8d580576463 100644
--- a/apps/sim/lib/workspace-events/emitter.ts
+++ b/apps/sim/lib/workspace-events/emitter.ts
@@ -1,13 +1,14 @@
import { createLogger } from '@sim/logger'
import { getActiveWorkflowContext } from '@sim/platform-authz/workflow'
import { generateShortId } from '@sim/utils/id'
-import type { WorkflowExecutionLog } from '@/lib/logs/types'
+import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
import {
isSimRuleEventType,
SIM_RULE_COOLDOWN_HOURS,
SIM_TRIGGER_PROVIDER,
} from '@/lib/workspace-events/constants'
import {
+ buildAgentToolErrorEventPayload,
buildDeployEventPayload,
buildExecutionEventPayload,
buildUndeployEventPayload,
@@ -23,6 +24,7 @@ import type {
SimEventPayload,
SimSubscription,
SimSubscriptionConfig,
+ SimToolError,
} from '@/lib/workspace-events/types'
const logger = createLogger('WorkspaceEventEmitter')
@@ -77,6 +79,33 @@ function matchesWorkflowScope(config: SimSubscriptionConfig, sourceWorkflowId: s
return config.workflowIds.includes(sourceWorkflowId)
}
+/** Collects canonical failed tool spans belonging directly to Agent blocks. */
+function collectAgentToolErrors(spans: TraceSpan[]): SimToolError[] {
+ const toolErrors: SimToolError[] = []
+
+ const visit = (span: TraceSpan): void => {
+ if (span.type === 'agent') {
+ for (const child of span.children ?? []) {
+ if (child.type !== 'tool' || child.status !== 'error' || !child.errorMessage) continue
+ toolErrors.push({
+ agentBlockId: span.blockId ?? span.id,
+ agentBlockName: span.name,
+ toolName: child.name,
+ toolCallId: child.toolCallId ?? null,
+ errorMessage: child.errorMessage,
+ durationMs: child.duration,
+ recovered: span.status === 'success',
+ })
+ }
+ }
+
+ for (const child of span.children ?? []) visit(child)
+ }
+
+ for (const span of spans) visit(span)
+ return toolErrors
+}
+
/**
* Emits workspace events for a completed workflow execution.
*
@@ -96,6 +125,7 @@ export async function emitExecutionCompletedEvent(log: WorkflowExecutionLog): Pr
if (subscriptions.length === 0) return
const executionData = (log.executionData ?? {}) as Record
+ const toolErrors = collectAgentToolErrors(log.executionData.traceSpans ?? [])
const context: ExecutionEventContext = {
workflowId: log.workflowId,
executionId: log.executionId,
@@ -117,6 +147,21 @@ export async function emitExecutionCompletedEvent(log: WorkflowExecutionLog): Pr
if (subscription.webhook.workflowId === log.workflowId) continue
if (!matchesWorkflowScope(config, log.workflowId)) continue
+ if (config.eventType === 'agent_tool_error') {
+ for (const toolError of toolErrors) {
+ await dispatchSimEvent(
+ subscription,
+ buildAgentToolErrorEventPayload({
+ workflowId: log.workflowId,
+ workflowName: workflowContext.workflow.name,
+ runId: log.executionId,
+ toolError,
+ })
+ )
+ }
+ continue
+ }
+
if (config.eventType === 'execution_success' && context.status !== 'success') continue
if (config.eventType === 'execution_error' && context.status !== 'error') continue
diff --git a/apps/sim/lib/workspace-events/payload.ts b/apps/sim/lib/workspace-events/payload.ts
index b05d237c987..3650f69f4d3 100644
--- a/apps/sim/lib/workspace-events/payload.ts
+++ b/apps/sim/lib/workspace-events/payload.ts
@@ -3,13 +3,13 @@ import { dollarsToCredits } from '@/lib/billing/credits/conversion'
import {
SIM_FINAL_OUTPUT_MAX_BYTES,
type SimEventType,
- type SimPlainEventType,
type SimRuleEventType,
} from '@/lib/workspace-events/constants'
import type {
ExecutionEventContext,
SimEventPayload,
SimRunSummary,
+ SimToolError,
} from '@/lib/workspace-events/types'
/**
@@ -46,10 +46,29 @@ function basePayload(params: {
cost: null,
finalOutput: null,
triggeringRun: null,
+ toolError: null,
version: null,
}
}
+/** Payload for one failed Agent tool invocation discovered at run completion. */
+export function buildAgentToolErrorEventPayload(params: {
+ workflowId: string
+ workflowName: string
+ runId: string
+ toolError: SimToolError
+}): SimEventPayload {
+ return {
+ ...basePayload({
+ event: 'agent_tool_error',
+ workflowId: params.workflowId,
+ workflowName: params.workflowName,
+ }),
+ runId: params.runId,
+ toolError: params.toolError,
+ }
+}
+
/** Run summary in user-facing units: cost in credits, finalOutput bounded. */
function summarizeRun(context: ExecutionEventContext): SimRunSummary {
return {
@@ -67,7 +86,7 @@ function summarizeRun(context: ExecutionEventContext): SimRunSummary {
* the condition that fired, so it nests under `triggeringRun`.
*/
export function buildExecutionEventPayload(params: {
- event: Exclude | SimRuleEventType
+ event: 'execution_success' | 'execution_error' | SimRuleEventType
workflowName: string
context: ExecutionEventContext
}): SimEventPayload {
diff --git a/apps/sim/lib/workspace-events/types.ts b/apps/sim/lib/workspace-events/types.ts
index 427894b772b..47211b1339f 100644
--- a/apps/sim/lib/workspace-events/types.ts
+++ b/apps/sim/lib/workspace-events/types.ts
@@ -46,6 +46,17 @@ export interface SimRunSummary {
finalOutput: unknown
}
+/** Safe, bounded metadata describing one failed tool invocation inside an Agent. */
+export interface SimToolError {
+ agentBlockId: string
+ agentBlockName: string
+ toolName: string
+ toolCallId: string | null
+ errorMessage: string
+ durationMs: number
+ recovered: boolean
+}
+
/**
* Wire payload delivered to a Sim trigger workflow. Keys must align with
* SIM_EVENT_PAYLOAD_FIELDS — enforced by tests on the payload builders.
@@ -60,5 +71,6 @@ export type SimEventPayload = Record & {
cost: number | null
finalOutput: unknown
triggeringRun: SimRunSummary | null
+ toolError: SimToolError | null
version: number | null
}
diff --git a/apps/sim/triggers/sim/workspace-event.test.ts b/apps/sim/triggers/sim/workspace-event.test.ts
index 9ddabb70d6f..fbe76836ac3 100644
--- a/apps/sim/triggers/sim/workspace-event.test.ts
+++ b/apps/sim/triggers/sim/workspace-event.test.ts
@@ -222,6 +222,12 @@ describe('sim workspace event outputs', () => {
}
})
+ it('agent tool errors expose the source run and safe tool metadata', () => {
+ expect(visibleOutputsFor('agent_tool_error')).toEqual(
+ ['event', 'runId', 'timestamp', 'toolError', 'workflowId', 'workflowName'].sort()
+ )
+ })
+
it('run-backed rule events expose the base fields plus the nested triggeringRun', () => {
for (const eventType of EXECUTION_BACKED.filter(
(type) => type !== 'execution_success' && type !== 'execution_error'
diff --git a/apps/sim/triggers/sim/workspace-event.ts b/apps/sim/triggers/sim/workspace-event.ts
index a2f885b2f02..dc03a0a0e2b 100644
--- a/apps/sim/triggers/sim/workspace-event.ts
+++ b/apps/sim/triggers/sim/workspace-event.ts
@@ -25,6 +25,7 @@ export const simWorkspaceEventTrigger: TriggerConfig = {
options: [
{ id: 'execution_error', label: 'Run Error', group: 'Events' },
{ id: 'execution_success', label: 'Run Success', group: 'Events' },
+ { id: 'agent_tool_error', label: 'Agent Tool Error', group: 'Events' },
{ id: 'workflow_deployed', label: 'Workflow Deployed', group: 'Events' },
{ id: 'workflow_undeployed', label: 'Workflow Undeployed', group: 'Events' },
{ id: 'consecutive_failures', label: 'Consecutive Failures', group: 'Alert Conditions' },