windmill-labs/windmill · warning · Error

Job was cancelled

Error message

Job was cancelled

What it means

JobManager.runWithProgress polls a Windmill job until completion. On each poll tick it checks the AbortController created for the run; if the job was cancelled (via cancel(jobId) or cancelAll(), which call controller.abort()), the next poll throws Error('Job was cancelled') and the promise rejects.

Source

Thrown at frontend/src/lib/services/JobManager.ts:86

		} = options

		const controller = new AbortController()
		const jobId = await jobRunner()

		this.activeJobs.set(jobId, {
			controller,
			startTime: Date.now()
		})

		try {
			onProgress?.({ status: 'running' })

			let finalResult: T | undefined = undefined

			await tryEvery({
				tryCode: async () => {
					if (controller.signal.aborted) {
						throw new Error('Job was cancelled')
					}

					let jobResult
					try {
						jobResult = await JobService.getCompletedJob({
							workspace,
							id: jobId
						})
					} catch (error) {
						throw error
					}

					const success = !!jobResult.success
					const status: JobStatus = {
						status: success ? 'success' : 'failure',
						result: jobResult.result,
						error: success ? undefined : (jobResult.result as any)?.error?.message || 'Job failed'
					}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Wrap the runWithProgress call in try/catch and treat Error with message 'Job was cancelled' as an expected, non-alerting outcome
  2. Avoid calling cancelAll()/cancel() on teardown if you still need the result, or await the promise before teardown
  3. Check controller state / isActive(jobId) before relying on the result

Example fix

// before
const result = await jobManager.runWithProgress(runJob, opts)
// after
try {
  const result = await jobManager.runWithProgress(runJob, opts)
} catch (err) {
  if (err instanceof Error && err.message === 'Job was cancelled') return // expected on teardown
  throw err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before awaiting, ensure no cancellation is pending
if (jobManager.isActive(jobId) === false || controller?.signal.aborted) {
  return // skip the run
}

Type guard

function isCancellationError(err: unknown): err is Error {
  return err instanceof Error && err.message === 'Job was cancelled'
}

Try / catch

try {
  const result = await jobManager.runWithProgress(runJob, opts)
} catch (err) {
  if (isCancellationError(err)) {
    return null // user-initiated or teardown cancellation: handle silently
  }
  throw err
}

Prevention

When it happens

Trigger: Calling runWithProgress, then invoking jobManager.cancel(jobId) or cancelAll() (e.g. component teardown, navigation away, user pressing a cancel button) while the polling loop is still running; or the abort signal already aborted when a poll tick fires.

Common situations: Svelte component destroyed mid-run without awaiting; user navigating away from an AI-generation/preview panel; a timeout elsewhere aborting all jobs; race where abort happens between job start and first poll.

Related errors


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