{"record":{"id":"aa3452da854561b5","repo":"n8n-io/n8n","slug":"run-timed-out-after-string-config-timeoutms-ms","errorCode":null,"errorMessage":"Run timed out after ${String(config.timeoutMs)}ms","messagePattern":"Run timed out after (.+?)ms","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/harness/chat-loop.ts","lineNumber":146,"sourceCode":"\n\t\t// Wait for observational-memory jobs (observer/reflector) before the next user turn\n\t\tawait waitForMemoryTasks(config);\n\n\t\t// Check if the main agent started a new run after background tasks completed\n\t\tawait delay(SSE_SETTLE_DELAY_MS);\n\t\tconst newRunStarts = countEvents(config.events, 'run-start');\n\t\tconst currentRunFinishes = countEvents(config.events, 'run-finish');\n\t\tif (newRunStarts <= currentRunFinishes) {\n\t\t\tbreak;\n\t\t}\n\n\t\tconfig.logger.verbose(\n\t\t\t`[${config.threadId}] Main agent resumed (run-start #${String(newRunStarts)}) -- waiting for completion`,\n\t\t);\n\n\t\tif (Date.now() - config.startTime > config.timeoutMs) {\n\t\t\tawait config.client.cancelRun(config.threadId).catch(() => {});\n\t\t\tthrow new Error(`Run timed out after ${String(config.timeoutMs)}ms`);\n\t\t}\n\t}\n}\n\nasync function waitForRunFinish(config: WaitConfig, expectedFinishCount: number): Promise<void> {\n\twhile (countEvents(config.events, 'run-finish') <= expectedFinishCount) {\n\t\tconst elapsed = Date.now() - config.startTime;\n\t\tif (elapsed > config.timeoutMs) {\n\t\t\tawait config.client.cancelRun(config.threadId).catch(() => {});\n\t\t\tthrow new Error(`Run timed out after ${String(config.timeoutMs)}ms`);\n\t\t}\n\n\t\tawait processConfirmationRequests(config);\n\t\tawait delay(POLL_INTERVAL_MS);\n\t}\n}\n\nasync function waitForBackgroundTasks(config: WaitConfig, timeoutMs: number): Promise<void> {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/harness/chat-loop.ts#L128-L164","documentation":"Thrown in the run-resumed loop of the chat driver after a run-finish, when the main agent has started a NEW run (run-start count exceeds run-finish count) and the total elapsed time since config.startTime has exceeded config.timeoutMs. Before throwing, the run is cancelled via client.cancelRun (errors swallowed). This guards the 'agent resumed itself' branch so a self-perpetuating loop cannot run forever.","triggerScenarios":"A multi-turn case where the agent keeps starting new runs (e.g. it loops on a tool, or background tasks trigger follow-up runs) such that the wall-clock budget runs out while the loop waits for the resumed run.","commonSituations":"An agent caught in a tool-call loop; an overly generous timeoutMs still exceeded by a pathological case; slow LLM responses across many resume cycles.","solutions":["Raise config.timeoutMs for the case if the run legitimately needs more time.","Investigate why the agent resumes (new run-start) repeatedly — a tool loop or repeated confirmation requests.","If transient, re-run the case; persistent timeouts indicate an agent-behavior or backend-liveness problem."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Budget check before resuming — surface a clear reason instead of a raw timeout.\nif (Date.now() - config.startTime > config.timeoutMs) {\n  throw new Error(`Aborting before resume: elapsed ${Date.now() - config.startTime}ms exceeds budget ${config.timeoutMs}ms; raise config.timeoutMs or investigate the resume loop.`);\n}","typeGuard":null,"tryCatchPattern":"// Treat timeout as a recoverable eval failure, not a crash.\ntry {\n  await driveChatLoop(config);\n} catch (e) {\n  if (e instanceof Error && /Run timed out after/.test(e.message)) {\n    recordCaseTimeout(config.threadId, config.timeoutMs);\n  }\n  throw e;\n}","preventionTips":["Size config.timeoutMs to the case's expected turn count and tool latency.","Investigate repeated resume cycles (run-start > run-finish) as an agent tool-loop bug.","Log resume counts so a slow drift toward the budget is visible before it trips."],"tags":["timeout","run-lifecycle","sse","chat-loop"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}