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
- Wrap the runWithProgress call in try/catch and treat Error with message 'Job was cancelled' as an expected, non-alerting outcome
- Avoid calling cancelAll()/cancel() on teardown if you still need the result, or await the promise before teardown
- 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
- Treat 'Job was cancelled' as an expected control-flow signal, not a failure to report
- Cancel jobs deliberately on component teardown and handle the resulting rejection
- Avoid calling cancelAll() when individual runs' results are still needed
- Track jobIds you cancel so you can correlate the rejection
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
- ${timeoutMessage}
- Job ${jobId} was not successful: ${JSON.stringify(error)}
- Failed to poll dependencies job ${jobId}: ${e?.message ?? e}
- Timed out waiting for flow ${id} to complete
- Failed to poll flow dependencies job ${jobId}: ${e?.message
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/6565b2922bbfe353.
Report an issue: GitHub.