From 7c516e6816eff5d6113da6c15195973a3321f70c Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 25 Feb 2026 04:45:39 -0800 Subject: [PATCH 1/2] fix(call-chain): x-sim-via propagation for API blocks and MCP tools --- .../docs/content/docs/en/execution/basics.mdx | 1 + apps/sim/app/api/mcp/tools/execute/route.ts | 9 +- apps/sim/executor/constants.ts | 1 - .../handlers/agent/agent-handler.test.ts | 156 +++++++----------- .../executor/handlers/agent/agent-handler.ts | 125 +------------- .../workflow/workflow-handler.test.ts | 8 +- .../handlers/workflow/workflow-handler.ts | 25 ++- .../execution/__tests__/call-chain.test.ts | 4 +- apps/sim/lib/execution/call-chain.ts | 2 +- apps/sim/lib/mcp/service.ts | 6 +- apps/sim/providers/types.ts | 1 + apps/sim/providers/utils.ts | 2 + apps/sim/tools/index.ts | 28 +++- 13 files changed, 121 insertions(+), 247 deletions(-) diff --git a/apps/docs/content/docs/en/execution/basics.mdx b/apps/docs/content/docs/en/execution/basics.mdx index 87c8df7781..1541831e77 100644 --- a/apps/docs/content/docs/en/execution/basics.mdx +++ b/apps/docs/content/docs/en/execution/basics.mdx @@ -97,6 +97,7 @@ Understanding these core principles will help you build better workflows: 3. **Smart Data Flow**: Outputs flow automatically to connected blocks 4. **Error Handling**: Failed blocks stop their execution path but don't affect independent paths 5. **State Persistence**: All block outputs and execution details are preserved for debugging +6. **Cycle Protection**: Workflows that call other workflows (via Workflow blocks, MCP tools, or API blocks) are tracked with a call chain. If the chain exceeds 25 hops, execution is stopped to prevent infinite loops ## Next Steps diff --git a/apps/sim/app/api/mcp/tools/execute/route.ts b/apps/sim/app/api/mcp/tools/execute/route.ts index d44839590f..f748069b88 100644 --- a/apps/sim/app/api/mcp/tools/execute/route.ts +++ b/apps/sim/app/api/mcp/tools/execute/route.ts @@ -3,6 +3,7 @@ import type { NextRequest } from 'next/server' import { getHighestPrioritySubscription } from '@/lib/billing/core/plan' import { getExecutionTimeout } from '@/lib/core/execution-limits' import type { SubscriptionPlan } from '@/lib/core/rate-limiter/types' +import { SIM_VIA_HEADER } from '@/lib/execution/call-chain' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpService } from '@/lib/mcp/service' import type { McpTool, McpToolCall, McpToolResult } from '@/lib/mcp/types' @@ -178,8 +179,14 @@ export const POST = withMcpAuth('read')( 'sync' ) + const simViaHeader = request.headers.get(SIM_VIA_HEADER) + const extraHeaders: Record = {} + if (simViaHeader) { + extraHeaders[SIM_VIA_HEADER] = simViaHeader + } + const result = await Promise.race([ - mcpService.executeTool(userId, serverId, toolCall, workspaceId), + mcpService.executeTool(userId, serverId, toolCall, workspaceId, extraHeaders), new Promise((_, reject) => setTimeout(() => reject(new Error('Tool execution timeout')), executionTimeout) ), diff --git a/apps/sim/executor/constants.ts b/apps/sim/executor/constants.ts index 7d3967732e..90c632fa5f 100644 --- a/apps/sim/executor/constants.ts +++ b/apps/sim/executor/constants.ts @@ -158,7 +158,6 @@ export const DEFAULTS = { MAX_LOOP_ITERATIONS: 1000, MAX_FOREACH_ITEMS: 1000, MAX_PARALLEL_BRANCHES: 20, - MAX_WORKFLOW_DEPTH: 10, MAX_SSE_CHILD_DEPTH: 3, EXECUTION_TIME: 0, TOKENS: { diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index 75c22e8be8..98560156e4 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -123,7 +123,6 @@ describe('AgentBlockHandler', () => { let handler: AgentBlockHandler let mockBlock: SerializedBlock let mockContext: ExecutionContext - let originalPromiseAll: any beforeEach(() => { handler = new AgentBlockHandler() @@ -135,8 +134,6 @@ describe('AgentBlockHandler', () => { configurable: true, }) - originalPromiseAll = Promise.all - mockBlock = { id: 'test-agent-block', metadata: { id: BlockType.AGENT, name: 'Test Agent' }, @@ -209,8 +206,6 @@ describe('AgentBlockHandler', () => { }) afterEach(() => { - Promise.all = originalPromiseAll - try { Object.defineProperty(global, 'window', { value: undefined, @@ -271,38 +266,7 @@ describe('AgentBlockHandler', () => { expect(result).toEqual(expectedOutput) }) - it('should preserve executeFunction for custom tools with different usageControl settings', async () => { - let capturedTools: any[] = [] - - Promise.all = vi.fn().mockImplementation((promises: Promise[]) => { - const result = originalPromiseAll.call(Promise, promises) - - result.then((tools: any[]) => { - if (tools?.length) { - capturedTools = tools.filter((t) => t !== null) - } - }) - - return result - }) - - mockExecuteProviderRequest.mockResolvedValueOnce({ - content: 'Using tools to respond', - model: 'mock-model', - tokens: { input: 10, output: 20, total: 30 }, - toolCalls: [ - { - name: 'auto_tool', - arguments: { input: 'test input for auto tool' }, - }, - { - name: 'force_tool', - arguments: { input: 'test input for force tool' }, - }, - ], - timing: { total: 100 }, - }) - + it('should preserve usageControl for custom tools and filter out "none"', async () => { const inputs = { model: 'gpt-4o', userPrompt: 'Test custom tools with different usageControl settings', @@ -372,13 +336,14 @@ describe('AgentBlockHandler', () => { await handler.execute(mockContext, mockBlock, inputs) - expect(Promise.all).toHaveBeenCalled() + const providerCall = mockExecuteProviderRequest.mock.calls[0] + const tools = providerCall[1].tools - expect(capturedTools.length).toBe(2) + expect(tools.length).toBe(2) - const autoTool = capturedTools.find((t) => t.name === 'auto_tool') - const forceTool = capturedTools.find((t) => t.name === 'force_tool') - const noneTool = capturedTools.find((t) => t.name === 'none_tool') + const autoTool = tools.find((t: any) => t.name === 'auto_tool') + const forceTool = tools.find((t: any) => t.name === 'force_tool') + const noneTool = tools.find((t: any) => t.name === 'none_tool') expect(autoTool).toBeDefined() expect(forceTool).toBeDefined() @@ -386,37 +351,6 @@ describe('AgentBlockHandler', () => { expect(autoTool.usageControl).toBe('auto') expect(forceTool.usageControl).toBe('force') - - expect(typeof autoTool.executeFunction).toBe('function') - expect(typeof forceTool.executeFunction).toBe('function') - - await autoTool.executeFunction({ input: 'test input' }) - expect(mockExecuteTool).toHaveBeenCalledWith( - 'function_execute', - expect.objectContaining({ - code: 'return { result: "auto tool executed", input }', - input: 'test input', - }), - false, // skipPostProcess - expect.any(Object) // execution context - ) - - await forceTool.executeFunction({ input: 'another test' }) - expect(mockExecuteTool).toHaveBeenNthCalledWith( - 2, // Check the 2nd call - 'function_execute', - expect.objectContaining({ - code: 'return { result: "force tool executed", input }', - input: 'another test', - }), - false, // skipPostProcess - expect.any(Object) // execution context - ) - - const providerCall = mockExecuteProviderRequest.mock.calls[0] - const requestBody = providerCall[1] - - expect(requestBody.tools.length).toBe(2) }) it('should filter out tools with usageControl set to "none"', async () => { @@ -1763,6 +1697,52 @@ describe('AgentBlockHandler', () => { expect(providerCallArgs[1].tools[0].name).toBe('search_files') }) + it('should pass callChain to executeProviderRequest for MCP cycle detection', async () => { + mockFetch.mockImplementation(() => + Promise.resolve({ ok: true, json: () => Promise.resolve({}) }) + ) + + const inputs = { + model: 'gpt-4o', + userPrompt: 'Search for files', + apiKey: 'test-api-key', + tools: [ + { + type: 'mcp', + title: 'search_files', + schema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search query' }, + }, + required: ['query'], + }, + params: { + serverId: 'mcp-search-server', + toolName: 'search_files', + serverName: 'search', + }, + usageControl: 'auto' as const, + }, + ], + } + + const contextWithCallChain = { + ...mockContext, + workspaceId: 'test-workspace-123', + workflowId: 'test-workflow-456', + callChain: ['wf-parent', 'test-workflow-456'], + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(contextWithCallChain, mockBlock, inputs) + + expect(mockExecuteProviderRequest).toHaveBeenCalled() + const providerCallArgs = mockExecuteProviderRequest.mock.calls[0][1] + expect(providerCallArgs.callChain).toEqual(['wf-parent', 'test-workflow-456']) + }) + it('should handle multiple MCP tools from the same server efficiently', async () => { const fetchCalls: any[] = [] @@ -2139,21 +2119,10 @@ describe('AgentBlockHandler', () => { expect(tools.length).toBe(0) }) - it('should use DB code for executeFunction when customToolId resolves', async () => { + it('should use DB schema when customToolId resolves', async () => { const toolId = 'custom-tool-123' mockFetchForCustomTool(toolId) - let capturedTools: any[] = [] - Promise.all = vi.fn().mockImplementation((promises: Promise[]) => { - const result = originalPromiseAll.call(Promise, promises) - result.then((tools: any[]) => { - if (tools?.length) { - capturedTools = tools.filter((t) => t !== null) - } - }) - return result - }) - const inputs = { model: 'gpt-4o', userPrompt: 'Format a report', @@ -2174,19 +2143,12 @@ describe('AgentBlockHandler', () => { await handler.execute(mockContext, mockBlock, inputs) - expect(capturedTools.length).toBe(1) - expect(typeof capturedTools[0].executeFunction).toBe('function') - - await capturedTools[0].executeFunction({ title: 'Q1', format: 'pdf' }) + expect(mockExecuteProviderRequest).toHaveBeenCalled() + const providerCall = mockExecuteProviderRequest.mock.calls[0] + const tools = providerCall[1].tools - expect(mockExecuteTool).toHaveBeenCalledWith( - 'function_execute', - expect.objectContaining({ - code: dbCode, - }), - false, - expect.any(Object) - ) + expect(tools.length).toBe(1) + expect(tools[0].name).toBe('formatReport') }) it('should not fetch from DB when no customToolId is present', async () => { diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 506fe712f9..98ee7a7625 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -33,7 +33,6 @@ import { stringifyJSON } from '@/executor/utils/json' import { executeProviderRequest } from '@/providers' import { getProviderFromModel, transformBlockTool } from '@/providers/utils' import type { SerializedBlock } from '@/serializer/types' -import { executeTool } from '@/tools' import { getTool, getToolAsync } from '@/tools/utils' const logger = createLogger('AgentBlockHandler') @@ -276,14 +275,12 @@ export class AgentBlockHandler implements BlockHandler { const userProvidedParams = tool.params || {} let schema = tool.schema - let code = tool.code let title = tool.title if (tool.customToolId) { const resolved = await this.fetchCustomToolById(ctx, tool.customToolId) if (resolved) { schema = resolved.schema - code = resolved.code title = resolved.title } else if (!schema) { logger.error(`Custom tool not found: ${tool.customToolId}`) @@ -296,7 +293,7 @@ export class AgentBlockHandler implements BlockHandler { return null } - const { filterSchemaForLLM, mergeToolParameters } = await import('@/tools/params') + const { filterSchemaForLLM } = await import('@/tools/params') const filteredSchema = filterSchemaForLLM(schema.function.parameters, userProvidedParams) @@ -313,43 +310,6 @@ export class AgentBlockHandler implements BlockHandler { usageControl: tool.usageControl || 'auto', } - if (code) { - base.executeFunction = async (callParams: Record) => { - const mergedParams = mergeToolParameters(userProvidedParams, callParams) - - const { blockData, blockNameMapping, blockOutputSchemas } = collectBlockData(ctx) - - const result = await executeTool( - 'function_execute', - { - code, - ...mergedParams, - timeout: tool.timeout ?? AGENT.DEFAULT_FUNCTION_TIMEOUT, - envVars: ctx.environmentVariables || {}, - workflowVariables: ctx.workflowVariables || {}, - blockData, - blockNameMapping, - blockOutputSchemas, - isCustomTool: true, - _context: { - workflowId: ctx.workflowId, - workspaceId: ctx.workspaceId, - userId: ctx.userId, - isDeployedContext: ctx.isDeployedContext, - enforceCredentialAccess: ctx.enforceCredentialAccess, - }, - }, - false, - ctx - ) - - if (!result.success) { - throw new Error(result.error || 'Function execution failed') - } - return result.output - } - } - return base } @@ -498,47 +458,6 @@ export class AgentBlockHandler implements BlockHandler { parameters: filteredSchema, params: userProvidedParams, usageControl: tool.usageControl || 'auto', - executeFunction: async (callParams: Record) => { - const headers = await buildAuthHeaders() - const execParams: Record = {} - if (ctx.userId) execParams.userId = ctx.userId - const execUrl = buildAPIUrl('/api/mcp/tools/execute', execParams) - - const execResponse = await fetch(execUrl.toString(), { - method: 'POST', - headers, - body: stringifyJSON({ - serverId, - toolName, - arguments: callParams, - workspaceId: ctx.workspaceId, - workflowId: ctx.workflowId, - toolSchema: tool.schema, - }), - }) - - if (!execResponse.ok) { - throw new Error( - `MCP tool execution failed: ${execResponse.status} ${execResponse.statusText}` - ) - } - - const result = await execResponse.json() - if (!result.success) { - throw new Error(result.error || 'MCP tool execution failed') - } - - return { - success: true, - output: result.data.output || {}, - metadata: { - source: 'mcp', - serverId, - serverName: serverName || serverId, - toolName, - }, - } - }, } } @@ -684,47 +603,6 @@ export class AgentBlockHandler implements BlockHandler { parameters: filteredSchema, params: userProvidedParams, usageControl: tool.usageControl || 'auto', - executeFunction: async (callParams: Record) => { - const headers = await buildAuthHeaders() - const discoverExecParams: Record = {} - if (ctx.userId) discoverExecParams.userId = ctx.userId - const execUrl = buildAPIUrl('/api/mcp/tools/execute', discoverExecParams) - - const execResponse = await fetch(execUrl.toString(), { - method: 'POST', - headers, - body: stringifyJSON({ - serverId, - toolName, - arguments: callParams, - workspaceId: ctx.workspaceId, - workflowId: ctx.workflowId, - toolSchema: mcpTool.inputSchema, - }), - }) - - if (!execResponse.ok) { - throw new Error( - `MCP tool execution failed: ${execResponse.status} ${execResponse.statusText}` - ) - } - - const result = await execResponse.json() - if (!result.success) { - throw new Error(result.error || 'MCP tool execution failed') - } - - return { - success: true, - output: result.data.output || {}, - metadata: { - source: 'mcp', - serverId, - serverName: mcpTool.serverName, - toolName, - }, - } - }, } } @@ -1082,6 +960,7 @@ export class AgentBlockHandler implements BlockHandler { blockData, blockNameMapping, isDeployedContext: ctx.isDeployedContext, + callChain: ctx.callChain, reasoningEffort: providerRequest.reasoningEffort, verbosity: providerRequest.verbosity, thinkingLevel: providerRequest.thinkingLevel, diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts index 661796db90..bddc16e821 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts @@ -108,18 +108,16 @@ describe('WorkflowBlockHandler', () => { ) }) - it('should enforce maximum depth limit', async () => { + it('should enforce maximum call chain depth limit', async () => { const inputs = { workflowId: 'child-workflow-id' } - // Create a deeply nested context (simulate 11 levels deep to exceed the limit of 10) const deepContext = { ...mockContext, - workflowId: - 'level1_sub_level2_sub_level3_sub_level4_sub_level5_sub_level6_sub_level7_sub_level8_sub_level9_sub_level10_sub_level11', + callChain: Array.from({ length: 25 }, (_, i) => `wf-${i}`), } await expect(handler.execute(deepContext, mockBlock, inputs)).rejects.toThrow( - '"child-workflow-id" failed: Maximum workflow nesting depth of 10 exceeded' + 'Maximum workflow call chain depth (25) exceeded' ) }) diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index 1666b51f2e..72bfa6781f 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -98,13 +98,17 @@ export class WorkflowBlockHandler implements BlockHandler { // workflow block execution, preventing cross-iteration child mixing in loop contexts. const instanceId = crypto.randomUUID() + const childCallChain = buildNextCallChain(ctx.callChain || [], workflowId) + const depthError = validateCallChain(childCallChain) + if (depthError) { + throw new ChildWorkflowError({ + message: depthError, + childWorkflowName, + }) + } + let childWorkflowSnapshotId: string | undefined try { - const currentDepth = (ctx.workflowId?.split('_sub_').length || 1) - 1 - if (currentDepth >= DEFAULTS.MAX_WORKFLOW_DEPTH) { - throw new Error(`Maximum workflow nesting depth of ${DEFAULTS.MAX_WORKFLOW_DEPTH} exceeded`) - } - if (ctx.isDeployedContext) { const hasActiveDeployment = await this.checkChildDeployment(workflowId) if (!hasActiveDeployment) { @@ -126,7 +130,7 @@ export class WorkflowBlockHandler implements BlockHandler { childWorkflowName = workflowMetadata?.name || childWorkflow.name || 'Unknown Workflow' logger.info( - `Executing child workflow: ${childWorkflowName} (${workflowId}) at depth ${currentDepth}` + `Executing child workflow: ${childWorkflowName} (${workflowId}), call chain depth ${ctx.callChain?.length || 0}` ) let childWorkflowInput: Record = {} @@ -168,15 +172,6 @@ export class WorkflowBlockHandler implements BlockHandler { ctx.onChildWorkflowInstanceReady?.(effectiveBlockId, instanceId, iterationContext) } - const childCallChain = buildNextCallChain(ctx.callChain || [], workflowId) - const depthError = validateCallChain(childCallChain) - if (depthError) { - throw new ChildWorkflowError({ - message: depthError, - childWorkflowName, - }) - } - const subExecutor = new Executor({ workflow: childWorkflow.serializedState, workflowInput: childWorkflowInput, diff --git a/apps/sim/lib/execution/__tests__/call-chain.test.ts b/apps/sim/lib/execution/__tests__/call-chain.test.ts index b793e1d531..fc937de142 100644 --- a/apps/sim/lib/execution/__tests__/call-chain.test.ts +++ b/apps/sim/lib/execution/__tests__/call-chain.test.ts @@ -19,8 +19,8 @@ describe('call-chain', () => { }) describe('MAX_CALL_CHAIN_DEPTH', () => { - it('equals 10', () => { - expect(MAX_CALL_CHAIN_DEPTH).toBe(10) + it('equals 25', () => { + expect(MAX_CALL_CHAIN_DEPTH).toBe(25) }) }) diff --git a/apps/sim/lib/execution/call-chain.ts b/apps/sim/lib/execution/call-chain.ts index 406274fa48..4e718c2352 100644 --- a/apps/sim/lib/execution/call-chain.ts +++ b/apps/sim/lib/execution/call-chain.ts @@ -7,7 +7,7 @@ */ export const SIM_VIA_HEADER = 'X-Sim-Via' -export const MAX_CALL_CHAIN_DEPTH = 10 +export const MAX_CALL_CHAIN_DEPTH = 25 /** * Parses the `X-Sim-Via` header value into an ordered list of workflow IDs. diff --git a/apps/sim/lib/mcp/service.ts b/apps/sim/lib/mcp/service.ts index 00ca59267b..69e7cc8117 100644 --- a/apps/sim/lib/mcp/service.ts +++ b/apps/sim/lib/mcp/service.ts @@ -170,7 +170,8 @@ class McpService { userId: string, serverId: string, toolCall: McpToolCall, - workspaceId: string + workspaceId: string, + extraHeaders?: Record ): Promise { const requestId = generateRequestId() const maxRetries = 2 @@ -187,6 +188,9 @@ class McpService { } const resolvedConfig = await this.resolveConfigEnvVars(config, userId, workspaceId) + if (extraHeaders && Object.keys(extraHeaders).length > 0) { + resolvedConfig.headers = { ...resolvedConfig.headers, ...extraHeaders } + } const client = await this.createClient(resolvedConfig) try { diff --git a/apps/sim/providers/types.ts b/apps/sim/providers/types.ts index 5df279077e..af5362c3c7 100644 --- a/apps/sim/providers/types.ts +++ b/apps/sim/providers/types.ts @@ -174,6 +174,7 @@ export interface ProviderRequest { verbosity?: string thinkingLevel?: string isDeployedContext?: boolean + callChain?: string[] /** Previous interaction ID for multi-turn Interactions API requests (deep research follow-ups) */ previousInteractionId?: string abortSignal?: AbortSignal diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index 03f664c074..1ee721e7b3 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -1110,6 +1110,7 @@ export function prepareToolExecution( blockData?: Record blockNameMapping?: Record isDeployedContext?: boolean + callChain?: string[] } ): { toolParams: Record @@ -1137,6 +1138,7 @@ export function prepareToolExecution( ...(request.isDeployedContext !== undefined ? { isDeployedContext: request.isDeployedContext } : {}), + ...(request.callChain ? { callChain: request.callChain } : {}), }, } : {}), diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index 9426d73103..8184cf7064 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -578,6 +578,25 @@ function isErrorResponse( return { isError: false } } +/** + * Checks whether a fully resolved URL points back to this Sim instance. + * Used to propagate cycle-detection headers on API blocks that target + * the platform's own workflow execution endpoints via absolute URL. + */ +function isSelfOriginUrl(url: string): boolean { + try { + const targetOrigin = new URL(url).origin + const publicOrigin = new URL(getBaseUrl()).origin + if (targetOrigin === publicOrigin) return true + + const internalOrigin = new URL(getInternalApiBaseUrl()).origin + if (targetOrigin === internalOrigin) return true + } catch { + return false + } + return false +} + /** * Add internal authentication token to headers if running on server * @param headers - Headers object to modify @@ -737,7 +756,8 @@ async function executeToolRequest( const headers = new Headers(requestParams.headers) await addInternalAuthIfNeeded(headers, isInternalRoute, requestId, toolId) - if (isInternalRoute) { + const shouldPropagateCallChain = isInternalRoute || isSelfOriginUrl(fullUrl) + if (shouldPropagateCallChain) { const callChain = params._context?.callChain as string[] | undefined if (callChain && callChain.length > 0) { headers.set(SIM_VIA_HEADER, serializeCallChain(callChain)) @@ -1123,6 +1143,12 @@ async function executeMcpTool( const workspaceId = params._context?.workspaceId || executionContext?.workspaceId const workflowId = params._context?.workflowId || executionContext?.workflowId const userId = params._context?.userId || executionContext?.userId + const callChain = + (params._context?.callChain as string[] | undefined) || executionContext?.callChain + + if (callChain && callChain.length > 0) { + headers[SIM_VIA_HEADER] = serializeCallChain(callChain) + } if (!workspaceId) { return { From b713516bdcde10a84c2bb50f5ea26837c91c64d6 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 25 Feb 2026 06:23:43 -0800 Subject: [PATCH 2/2] addres bugbot comment --- apps/sim/executor/handlers/agent/agent-handler.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 98ee7a7625..33ae22f5ec 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -319,7 +319,7 @@ export class AgentBlockHandler implements BlockHandler { private async fetchCustomToolById( ctx: ExecutionContext, customToolId: string - ): Promise<{ schema: any; code: string; title: string } | null> { + ): Promise<{ schema: any; title: string } | null> { if (typeof window !== 'undefined') { try { const { getCustomTool } = await import('@/hooks/queries/custom-tools') @@ -327,7 +327,6 @@ export class AgentBlockHandler implements BlockHandler { if (tool) { return { schema: tool.schema, - code: tool.code || '', title: tool.title, } } @@ -376,7 +375,6 @@ export class AgentBlockHandler implements BlockHandler { return { schema: tool.schema, - code: tool.code || '', title: tool.title, } } catch (error) {