windmill-labs/windmill · error
Timed out after ${Math.round(CASCADE_JOB_TIMEOUT_MS / 60000)
Error message
Timed out after ${Math.round(CASCADE_JOB_TIMEOUT_MS / 60000)}min waiting for job ${jobId} to finish What it means
cascadeRun.ts polls a launched Windmill job until it reaches a terminal state (success/failure/completed). makeWaitJobTerminal throws this timeout error when the job has not become terminal within CASCADE_JOB_TIMEOUT_MS (expressed in minutes in the message), while tolerating transient polling errors.
Source
Thrown at frontend/src/lib/components/assets/AssetGraph/cascadeRun.ts:56
export function makeWaitJobTerminal(
workspace: string
): (jobId: string) => Promise<'success' | 'failure'> {
return async function waitJobTerminal(jobId: string): Promise<'success' | 'failure'> {
const deadline = Date.now() + CASCADE_JOB_TIMEOUT_MS
while (Date.now() < deadline) {
try {
const r = await JobService.getCompletedJobResultMaybe({
workspace,
id: jobId,
getStarted: false
})
if (r.completed) return r.success ? 'success' : 'failure'
} catch {
// transient — retry on the next tick
}
await new Promise((res) => setTimeout(res, CASCADE_POLL_INTERVAL_MS))
}
throw new Error(
`Timed out after ${Math.round(CASCADE_JOB_TIMEOUT_MS / 60000)}min waiting for job ${jobId} to finish`
)
}
}
// Build a per-script launch function. When `resolveLocal(path)` yields content,
// the script runs as a preview of that local content (no deploy); otherwise it
// runs the deployed version by path. Always passes `_wmill_skip_asset_dispatch`.
export function makeLaunch(opts: {
workspace: string
resolveLocal?: (path: string) => LocalScriptContent | undefined
tempScriptRefs?: Record<string, string>
// Extra run args for a specific node (e.g. the uploaded S3Object bound to a
// `data_upload` cascade root). Merged over `_wmill_skip_asset_dispatch`; all
// other nodes run with empty inputs as before.
argsFor?: (path: string) => Record<string, any> | undefined
onLaunched?: (path: string, jobId: string) => void
}): (path: string) => Promise<string> {View on GitHub (pinned to e474e8803c)
Solutions
- Check the job ${jobId} in the Windmill UI runs view to see if it is queued, running, hung, or failed silently
- Scale up workers / clear the queue so the job actually runs within the timeout window
- Add an internal timeout or `timeout` setting to the script itself so a hung step fails terminally instead of running forever
- Increase CASCADE_JOB_TIMEOUT_MS in cascadeRun.ts if your jobs legitimately take longer than the default
- Verify the poll requests hit the right workspace and aren't erroring permanently (the catch treats all poll failures as transient)
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check job state before committing to the wait
const job = await JobService.getJob({ workspace, id: jobId })
if (job.completed && !job.success) throw new Error(`job ${jobId} already failed: ${job.logs ?? ''}`) Type guard
function isTerminal(job: { completed?: boolean }): job is { completed: true } {
return job.completed === true
} Try / catch
try {
await waitJobTerminal(jobId)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Timed out after')) {
// inspect the job in the UI, cancel or retry with a longer window
await JobService.cancelJob({ workspace, id: jobId }).catch(() => {})
throw new Error(`cascade job ${jobId} hung; cancelled`, { cause: e })
}
throw e
} Prevention
- Set per-step timeouts inside long-running scripts so they always reach a terminal state
- Keep enough workers provisioned so cascade jobs don't sit queued past the timeout
- Monitor the poll endpoint — repeated transient failures mean the job state will never be observed
- Raise CASCADE_JOB_TIMEOUT_MS for pipelines with legitimately long jobs
When it happens
Trigger: A run triggered by the asset-graph cascade launched a job whose id never reaches a completed state before the timeout window elapses — the job is queued for a long time, the worker pool is saturated, the job hangs (e.g. waiting on an external service), or the workspace/job was cancelled in a way that never marks completion.
Common situations: Long queue on a self-hosted instance with few workers; a script stuck on a slow API call without its own timeout; gateway/auth issues making the poll endpoint permanently return non-completed states (poll errors are swallowed as 'transient'); running against the wrong workspace so the completed job is never seen.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to poll dependencies job ${jobId}: ${e?.message ?? e}
- Timed out waiting for job ${jobId}
- ${timeoutMessage}
- reached timeout
- Job ${jobId} was not successful: ${JSON.stringify(error)}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/75b99573096e1c7e.
Report an issue: GitHub.