windmill-labs/windmill · error

Giving up polling job ${jobId} after ${MAX_CONSECUTIVE_POLL_

Error message

Giving up polling job ${jobId} after ${MAX_CONSECUTIVE_POLL_ERRORS} consecutive errors. Last error: ${err?.message ?? err}

What it means

pollJobWithQueueLogging in cli/src/utils/job_polling.ts polls getCompletedJobResultMaybe in a loop until a job finishes. Each failed status check increments a consecutive-error counter (successful polls reset it to 0); when MAX_CONSECUTIVE_POLL_ERRORS consecutive errors occur the loop throws this error containing the jobId and the last underlying error message. It signals that the CLI could not confirm the job's outcome, not that the job itself failed.

Source

Thrown at cli/src/utils/job_polling.ts:117

        id: jobId,
        getStarted: false,
      });

      consecutiveErrors = 0;

      if (maybe.completed) {
        return { result: maybe.result, success: maybe.success ?? false };
      }
    } catch (err: any) {
      consecutiveErrors++;
      log.warn(
        colors.yellow(
          `${label}${jobId}: error checking job status (${consecutiveErrors}/${MAX_CONSECUTIVE_POLL_ERRORS}): ${err?.message ?? err}`,
        ),
      );
      lastHeartbeatAt = Date.now();
      if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
        throw new Error(
          `Giving up polling job ${jobId} after ${MAX_CONSECUTIVE_POLL_ERRORS} consecutive errors. Last error: ${err?.message ?? err}`,
        );
      }
    }

    if (Date.now() - lastQueueLogAt >= QUEUE_LOG_INTERVAL_MS) {
      lastQueueLogAt = Date.now();
      const logged = await logQueueStatus(workspace, jobId, label);
      if (logged) lastHeartbeatAt = Date.now();
    }

    if (Date.now() - lastHeartbeatAt >= HEARTBEAT_INTERVAL_MS) {
      lastHeartbeatAt = Date.now();
      log.info(
        colors.gray(
          `${label}${jobId}: still polling, queue status unavailable...`,
        ),
      );

View on GitHub (pinned to e474e8803c)

Solutions

  1. Read 'Last error' in the message to identify the root cause (network vs HTTP status vs auth) and fix that first
  2. Re-run the command once the backend is reachable — the job may have completed server-side; check its result in the UI or via `wmill job result <jobId>`
  3. Verify connectivity: curl the instance's /api endpoint from the same machine
  4. Re-login (wmill login) if the token expired mid-run
  5. For persistent self-hosted flakiness, address the upstream instability (proxy timeouts, worker restarts) rather than retrying the CLI

Example fix

// before: transient backend restart aborts the command
// Giving up polling job X after N consecutive errors. Last error: fetch failed

// after: catch and re-check the job result before retrying the whole command
try {
  await pollForJobResult(workspace, jobId);
} catch (e) {
  const maybe = await wmill.getCompletedJobResultMaybe({ workspace, id: jobId });
  if (!maybe.completed) throw e; // job genuinely unresolved; retry later
}
Defensive patterns

Strategy: retry

Validate before calling

// before starting a long operation, confirm the instance is reachable and accepting API calls
const ping = await fetch(`${process.env.BASE_URL}/api/workspaces/list`, { headers: authHeaders });
if (!ping.ok) throw new Error(`Windmill API unhealthy (HTTP ${ping.status}); polling would exhaust retries`);

Type guard

function isPollGiveUpError(err: unknown): err is Error & { jobId?: string } {
  return err instanceof Error && /^Giving up polling job /.test(err.message);
}

Try / catch

try {
  await pollForJobResult(workspace, jobId);
} catch (e) {
  if (isPollGiveUpError(e)) {
    // jobId is in the message; check the job out-of-band before retrying
    const id = e.message.match(/job ([^ ]+) /)?.[1];
    const maybe = await wmill.getCompletedJobResultMaybe({ workspace, id });
    if (!maybe.completed) await sleep(5000); // then retry the poll
  } else throw e;
}

Prevention

When it happens

Trigger: Called by generateInlineScriptLock, updateFlow, pollForJobResult, fetchScriptLock etc.; thrown when MAX_CONSECUTIVE_POLL_ERRORS consecutive calls to wmill.getCompletedJobResultMaybe({workspace, id: jobId}) reject — e.g. connection resets, 5xx/429 responses, or workspace/token errors — in a row without a single successful poll in between.

Common situations: Backend restarted or briefly down during a long flow deploy; flaky VPN/network to a self-hosted instance; server overloaded returning 502/503; auth token expiring mid-poll; job deleted server-side so every poll 404s; polling through an unstable reverse proxy.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/2a864bd75c20f887. Report an issue: GitHub.