feat(timeouts): execution timeout limits (#3120)

* feat(timeouts): execution timeout limits

* fix type issues

* add to docs

* update stale exec cleanup route

* update more callsites

* update tests

* address bugbot comments

* remove import expression

* support streaming and async paths'

* fix streaming path

* add hitl and workflow handler

* make sync path match

* consolidate

* timeout errors

* validation errors typed

* import order

* Merge staging into feat/timeout-lims

Resolved conflicts:
- stt/route.ts: Keep both execution timeout and security imports
- textract/parse/route.ts: Keep both execution timeout and validation imports
- use-workflow-execution.ts: Keep cancellation console entry from feature branch
- input-validation.ts: Remove server functions (moved to .server.ts in staging)
- tools/index.ts: Keep execution timeout, use .server import for security

* make run from block consistent

* revert console update change

* fix subflow errors

* clean up base 64 cache correctly

* update docs

* consolidate workflow execution and run from block hook code

* remove unused constant

* fix cleanup base64 sse

* fix run from block tracespan
This commit is contained in:
Vikhyath Mondreti
2026-02-04 10:26:36 -08:00
committed by GitHub
parent f811594875
commit a627faabe7
53 changed files with 1149 additions and 484 deletions

View File

@@ -1,3 +1,4 @@
import { getMaxExecutionTimeout } from '@/lib/core/execution-limits'
import type { LoopType, ParallelType } from '@/lib/workflows/types'
/**
@@ -187,8 +188,12 @@ export const HTTP = {
export const AGENT = {
DEFAULT_MODEL: 'claude-sonnet-4-5',
DEFAULT_FUNCTION_TIMEOUT: 600000,
REQUEST_TIMEOUT: 600000,
get DEFAULT_FUNCTION_TIMEOUT() {
return getMaxExecutionTimeout()
},
get REQUEST_TIMEOUT() {
return getMaxExecutionTimeout()
},
CUSTOM_TOOL_PREFIX: 'custom_',
} as const

View File

@@ -162,6 +162,8 @@ export class ExecutionEngine {
}
}
this.finalizeIncompleteLogs()
const errorMessage = normalizeError(error)
logger.error('Execution failed', { error: errorMessage })

View File

@@ -14,7 +14,7 @@ const sleep = async (ms: number, options: SleepOptions = {}): Promise<boolean> =
const { signal, executionId } = options
const useRedis = isRedisCancellationEnabled() && !!executionId
if (!useRedis && signal?.aborted) {
if (signal?.aborted) {
return false
}
@@ -27,7 +27,7 @@ const sleep = async (ms: number, options: SleepOptions = {}): Promise<boolean> =
const cleanup = () => {
if (mainTimeoutId) clearTimeout(mainTimeoutId)
if (checkIntervalId) clearInterval(checkIntervalId)
if (!useRedis && signal) signal.removeEventListener('abort', onAbort)
if (signal) signal.removeEventListener('abort', onAbort)
}
const onAbort = () => {
@@ -37,6 +37,10 @@ const sleep = async (ms: number, options: SleepOptions = {}): Promise<boolean> =
resolve(false)
}
if (signal) {
signal.addEventListener('abort', onAbort, { once: true })
}
if (useRedis) {
checkIntervalId = setInterval(async () => {
if (resolved) return
@@ -49,8 +53,6 @@ const sleep = async (ms: number, options: SleepOptions = {}): Promise<boolean> =
}
} catch {}
}, CANCELLATION_CHECK_INTERVAL_MS)
} else if (signal) {
signal.addEventListener('abort', onAbort, { once: true })
}
mainTimeoutId = setTimeout(() => {

View File

@@ -126,6 +126,7 @@ export class WorkflowBlockHandler implements BlockHandler {
workspaceId: ctx.workspaceId,
userId: ctx.userId,
executionId: ctx.executionId,
abortSignal: ctx.abortSignal,
},
})

View File

@@ -229,6 +229,10 @@ export function addSubflowErrorLog(
}
ctx.blockLogs.push(blockLog)
if (contextExtensions?.onBlockStart) {
contextExtensions.onBlockStart(blockId, blockName, blockType, execOrder)
}
if (contextExtensions?.onBlockComplete) {
contextExtensions.onBlockComplete(blockId, blockName, blockType, {
input: inputData,