From 66998bf603b8e34cb90636602bb45c75fabd6ed0 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Mon, 29 Dec 2025 19:53:03 +0800 Subject: [PATCH 1/2] fix(http): resolve 5-minute timeout limit for API block requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: Bun/Node.js fetch has a hardcoded 5-minute (300s) default timeout that cannot be overridden by AbortSignal.timeout(). Changes: - Add parseTimeout() function to validate user-configurable timeouts - Add timeout: false to all fetch calls to disable Bun's default limit - Use AbortSignal.timeout() for user-configurable timeout control - Add maxDuration=600 to /api/proxy and /api/workflows/[id]/execute routes - Add timeout field to RequestParams interface This allows API block requests to run up to 10 minutes as configured by the user, fixing issue #2242. Tested: 6-minute API request completes successfully with timeout: false 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/sim/app/api/proxy/route.ts | 11 ++++ .../app/api/workflows/[id]/execute/route.ts | 2 + apps/sim/tools/http/types.ts | 6 +++ apps/sim/tools/index.ts | 50 ++++++++++++++++++- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/api/proxy/route.ts b/apps/sim/app/api/proxy/route.ts index 24702aa48f..f4dddbd56f 100644 --- a/apps/sim/app/api/proxy/route.ts +++ b/apps/sim/app/api/proxy/route.ts @@ -13,6 +13,13 @@ import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils' const logger = createLogger('ProxyAPI') +/** + * Next.js route configuration for long-running API requests + * maxDuration: 10 minutes to support user-configurable timeouts up to 10 min + */ +export const runtime = 'nodejs' +export const maxDuration = 600 + const proxyPostSchema = z.object({ toolId: z.string().min(1, 'toolId is required'), params: z.record(z.any()).optional().default({}), @@ -212,6 +219,8 @@ export async function GET(request: Request) { try { const pinnedUrl = createPinnedUrl(targetUrl, urlValidation.resolvedIP!) + // timeout: false disables Bun/Node.js default 5-minute timeout + // maxDuration handles the overall route timeout const response = await fetch(pinnedUrl, { method: method, headers: { @@ -220,6 +229,8 @@ export async function GET(request: Request) { Host: urlValidation.originalHostname!, }, body: body || undefined, + // @ts-expect-error - Bun-specific option to disable default 5-minute timeout + timeout: false, }) const contentType = response.headers.get('content-type') || '' diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 5d1a7d7a02..7da5686737 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -50,6 +50,8 @@ const ExecuteWorkflowSchema = z.object({ export const runtime = 'nodejs' export const dynamic = 'force-dynamic' +// Allow up to 10 minutes for workflow execution with long-running API calls +export const maxDuration = 600 function resolveOutputIds( selectedOutputs: string[] | undefined, diff --git a/apps/sim/tools/http/types.ts b/apps/sim/tools/http/types.ts index aee763469a..42bbba983d 100644 --- a/apps/sim/tools/http/types.ts +++ b/apps/sim/tools/http/types.ts @@ -8,6 +8,12 @@ export interface RequestParams { params?: TableRow[] pathParams?: Record formData?: Record + /** + * Request timeout in milliseconds. + * Default: 120000 (2 minutes) + * Maximum: 600000 (10 minutes) + */ + timeout?: number } export interface RequestResponse extends ToolResponse { diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index b0f6f6fd36..eeb426cc7d 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -43,6 +43,34 @@ function normalizeToolId(toolId: string): string { */ const MAX_REQUEST_BODY_SIZE_BYTES = 10 * 1024 * 1024 // 10MB +/** + * Default timeout for HTTP requests in milliseconds (2 minutes) + */ +const DEFAULT_TIMEOUT_MS = 120000 + +/** + * Maximum allowed timeout for HTTP requests in milliseconds (10 minutes) + */ +const MAX_TIMEOUT_MS = 600000 + +/** + * Parses and validates a timeout value, ensuring it falls within acceptable bounds. + * @param timeout - The timeout value to parse (number or string in ms) + * @returns The parsed timeout in milliseconds, clamped to MAX_TIMEOUT_MS + */ +function parseTimeout(timeout: number | string | undefined): number { + if (typeof timeout === 'number' && timeout > 0) { + return Math.min(timeout, MAX_TIMEOUT_MS) + } + if (typeof timeout === 'string') { + const parsed = Number.parseInt(timeout, 10) + if (!Number.isNaN(parsed) && parsed > 0) { + return Math.min(parsed, MAX_TIMEOUT_MS) + } + } + return DEFAULT_TIMEOUT_MS +} + /** * User-friendly error message for body size limit exceeded */ @@ -650,11 +678,20 @@ async function handleInternalRequest( // Check request body size before sending to detect potential size limit issues validateRequestBodySize(requestParams.body, requestId, toolId) - // Prepare request options - const requestOptions = { + // Parse timeout from params (user-configurable via API block) + const timeoutMs = parseTimeout(params.timeout) + logger.info(`[${requestId}] Request timeout for ${toolId}: ${timeoutMs}ms`) + + // Prepare request options with timeout support + // Note: timeout: false disables Bun/Node.js default 5-minute timeout + // AbortSignal.timeout provides user-configurable timeout control + const requestOptions: RequestInit & { timeout?: boolean } = { method: requestParams.method, headers: headers, body: requestParams.body, + // @ts-expect-error - Bun-specific option to disable default 5-minute timeout + timeout: false, + signal: AbortSignal.timeout(timeoutMs), } const response = await fetch(fullUrl, requestOptions) @@ -870,10 +907,19 @@ async function handleProxyRequest( // Check request body size before sending validateRequestBodySize(body, requestId, `proxy:${toolId}`) + // Parse timeout from params (user-configurable via API block) + const timeoutMs = parseTimeout(params.timeout) + logger.info(`[${requestId}] Proxy request timeout for ${toolId}: ${timeoutMs}ms`) + + // Note: timeout: false disables Bun/Node.js default 5-minute timeout + // AbortSignal.timeout provides user-configurable timeout control const response = await fetch(proxyUrl, { method: 'POST', headers, body, + // @ts-expect-error - Bun-specific option to disable default 5-minute timeout + timeout: false, + signal: AbortSignal.timeout(timeoutMs), }) if (!response.ok) { From 9e2f717774e8d045d47e5bbda87cae1ec58bfc67 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Tue, 30 Dec 2025 19:41:09 +0800 Subject: [PATCH 2/2] fix: use @ts-ignore instead of @ts-expect-error for Bun timeout option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @ts-expect-error directive fails in CI because it reports 'unused directive' when the error doesn't exist in the Node.js type definitions. Using @ts-ignore unconditionally ignores the type error, which is the desired behavior for Bun-specific options. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index eeb426cc7d..2f06d210d5 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -689,7 +689,7 @@ async function handleInternalRequest( method: requestParams.method, headers: headers, body: requestParams.body, - // @ts-expect-error - Bun-specific option to disable default 5-minute timeout + // @ts-ignore - Bun-specific option to disable default 5-minute timeout (not in standard RequestInit types) timeout: false, signal: AbortSignal.timeout(timeoutMs), } @@ -917,7 +917,7 @@ async function handleProxyRequest( method: 'POST', headers, body, - // @ts-expect-error - Bun-specific option to disable default 5-minute timeout + // @ts-ignore - Bun-specific option to disable default 5-minute timeout (not in standard RequestInit types) timeout: false, signal: AbortSignal.timeout(timeoutMs), })