{"record":{"id":"26fe60bb61b54d36","repo":"windmill-labs/windmill","slug":"timeoutmessage","errorCode":null,"errorMessage":"${timeoutMessage}","messagePattern":"\\$\\{timeoutMessage\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/services/JobManager.ts","lineNumber":124,"sourceCode":"\t\t\t\t\tonProgress?.(status)\n\n\t\t\t\t\tfinalResult = jobResult.result as T\n\n\t\t\t\t\treturn jobResult.result as T\n\t\t\t\t},\n\t\t\t\ttimeoutCode: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait JobService.cancelQueuedJob({\n\t\t\t\t\t\t\tworkspace,\n\t\t\t\t\t\t\tid: jobId,\n\t\t\t\t\t\t\trequestBody: { reason: timeoutMessage }\n\t\t\t\t\t\t})\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error('Failed to cancel job:', err)\n\t\t\t\t\t}\n\n\t\t\t\t\tonProgress?.({ status: 'failure', error: timeoutMessage })\n\t\t\t\t\tthrow new Error(timeoutMessage)\n\t\t\t\t},\n\t\t\t\tinterval,\n\t\t\t\ttimeout\n\t\t\t})\n\n\t\t\treturn finalResult as T\n\t\t} finally {\n\t\t\tthis.activeJobs.delete(jobId)\n\t\t}\n\t}\n\n\tcancel(jobId: string) {\n\t\tconst entry = this.activeJobs.get(jobId)\n\t\tif (entry) {\n\t\t\tentry.controller.abort()\n\t\t\tthis.activeJobs.delete(jobId)\n\t\t}\n\t}","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/services/JobManager.ts#L106-L142","documentation":"runWithProgress enforces a timeout (default 60s, configurable via JobOptions.timeout). When tryEvery's deadline expires, the timeoutCode callback first attempts to cancel the queued job server-side (JobService.cancelQueuedJob), reports onProgress({status:'failure'}), and throws Error(timeoutMessage), by default `Job timed out after Ns`.","triggerScenarios":"The Windmill job (script/flow/AI generation) takes longer than options.timeout to complete while getCompletedJob keeps returning a non-completed job; interval polling never sees success before the deadline.","commonSituations":"Long-running flows or heavy scripts polled from the UI with the default 60s timeout; slow worker queues under load; AI generation jobs exceeding the default; passing a timeout smaller than the job's realistic duration.","solutions":["Increase the timeout option passed to runWithProgress to exceed the job's expected duration","Investigate why the job is slow (worker availability, job logs in the Windmill UI)","Reduce the job's work or split it into smaller steps","Handle the timeout error in the caller and offer retry/extend UI"],"exampleFix":"// before\nconst result = await jobManager.runWithProgress(runJob, { workspace, onProgress }) // default 60s timeout\n// after\nconst result = await jobManager.runWithProgress(runJob, {\n  workspace,\n  onProgress,\n  timeout: 300000, // 5 min\n  timeoutMessage: 'Generation timed out after 5 minutes'\n})","handlingStrategy":"retry","validationCode":"// size the timeout to the job before starting\nconst expectedMs = estimateJobDuration(jobKind)\nif (expectedMs > (options.timeout ?? 60000)) {\n  options.timeout = expectedMs * 2\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await jobManager.runWithProgress(runJob, { ...opts, timeout: 300000 })\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Job timed out')) {\n    onTimeout(err.message) // offer retry or poll the job manually via JobService\n    return\n  }\n  throw err\n}","preventionTips":["Set timeout generously above the job's worst-case duration","Surface onProgress failure states so users see slowness before the hard timeout","Check worker capacity/queue depth when timeouts cluster","Split long flows into smaller steps instead of one long job"],"tags":["timeout","async","polling","jobs"],"backgroundTag":"operation-timeout","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}