windmill-labs/windmill · error · Error
Timed out waiting for job ${id}
Error message
Timed out waiting for job ${id} What it means
Thrown by waitJob when polling a Windmill job (scheduled pipeline run) exceeds the wait budget without the job reaching a completed state. The helper polls job status every 100ms (transient fetch errors are swallowed and retried); if the loop exhausts, it gives up with this timeout error naming the job id.
Source
Thrown at cli/src/commands/pipeline/pipeline.ts:430
): Promise<{ ok: boolean; result?: unknown }> {
const MAX_RETRIES = 6000; // ~10min at 100ms
for (let i = 0; i < MAX_RETRIES; i++) {
try {
const r = await wmill.getCompletedJobResultMaybe({
workspace,
id,
getStarted: false,
});
// A completed job without an explicit `success: true` is a failure
// (mirrors the frontend `waitJobTerminal`): the cascade only advances on
// a confirmed success.
if (r.completed) return { ok: r.success === true, result: r.result };
} catch {
// transient — retry
}
await new Promise((res) => setTimeout(res, 100));
}
throw new Error(`Timed out waiting for job ${id}`);
}
// Render a failed job's `{error: {name, message}}` result for the terminal, so
// the user sees WHY the cascade stopped without opening the UI. Result shapes
// vary (structured data-test payloads, plain strings), so fall back to raw
// JSON when the canonical error shape is absent.
function formatJobFailure(result: unknown): string | undefined {
if (result == null) return undefined;
const err = (result as any)?.error;
if (err && typeof err === "object") {
const name = typeof err.name === "string" ? err.name : undefined;
const message = typeof err.message === "string" ? err.message : undefined;
if (message) return name ? `${name}: ${message}` : message;
}
if (typeof result === "string") return result;
try {
return JSON.stringify(result, null, 2);
} catch {View on GitHub (pinned to e474e8803c)
Solutions
- Check the job in the Windmill UI (`/runs/get/<id>`) — if still running, increase the wait timeout option and re-run.
- If the job is suspended, approve/resume it in the UI and poll manually.
- Check worker availability (queue depth, worker count) if the job never started.
- Fetch the final result yourself with `wmill job get-result <id>` if the job has since completed.
Example fix
// before (CLI default wait) wmill pipeline run f/pipeline // after: longer wait via the command's timeout option or poll manually wmill pipeline run f/pipeline --wait-timeout 3600
Defensive patterns
Strategy: retry
Validate before calling
// Nothing to validate pre-call (timeout is runtime); bound expectations instead:
// check the job exists and is not suspended before waiting
const job = await wmill.getJob({ workspace: ws, id });
if (job.type === "suspend") throw new Error("Job is suspended — approve it before waiting"); Try / catch
try {
const r = await waitJob(id);
} catch (e: any) {
if (String(e.message).startsWith("Timed out waiting for job")) {
const jid = e.message.split("job ")[1];
// inspect the job's real state before retrying with a longer budget
const state = await wmill.getJob({ workspace: ws, id: jid });
console.error(`Job ${jid} still ${state.running ? "running" : "queued/suspended"}; check the UI`);
}
throw e;
} Prevention
- Increase the wait timeout for pipelines with known-long steps.
- Check worker availability/queue depth if jobs routinely sit queued.
- Never point waitJob at suspended jobs — approve them via the UI or API first.
- Inspect /runs/get/<id> in the UI to distinguish slow vs stuck jobs.
When it happens
Trigger: Running a pipeline whose jobs take longer than waitJob's polling limit (long-running scripts, stuck/queued jobs on a saturated worker), or a job id that will never complete because it is suspended (waiting for approval/sleep) or was cancelled while the poller kept missing completion.
Common situations: Pipelines with heavy S3/data steps exceeding the default timeout; all workers busy so the job sits in queue; a suspended (human-approval) step never approved; wrong instance checked (job completed on another workspace).
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out waiting for flow ${id} to complete
- Timed out waiting for job ${id} to complete
- Giving up polling job ${jobId} after ${MAX_CONSECUTIVE_POLL_
- Failed to poll dependencies job ${jobId}: ${e?.message ?? e}
- Failed to poll dependencies job ${jobId}: ${e?.message ?? e}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/776d3910632d234d.
Report an issue: GitHub.