Skip to content
Get started
Get started
Worker Events: completed, failed, stalled
guide · worker

What the worker tells you.

Metrics, alerting and audit trails all hang off the same handful of events. Here is each one, when it fires and exactly what it hands your listener.

worker.on('completed', (job, result) => {
console.log(`Completed: ${job.id}`, result);
});
worker.on('failed', (job, error) => {
console.error(`Failed: ${job.id}`, error.message);
});
worker.on('progress', (job, progress) => {
console.log(`Progress: ${job.id} - ${progress}%`);
});
worker.on('error', (error) => console.error(error)); // always attach

Rust and Elixir telemetry covers connection, command, retry, timeout and error lifecycle, but it does not synthesize completed/failed listener events. Record per-job outcomes inside the processor. See the SDK guide.

All Bun client events are fully typed. The complete list (external SDK event sets are listed in the SDK guide):

EventCallback ParametersDescription
ready()Worker started polling
active(job: Job<T>)Job started processing
completed(job: Job<T>, result: R)Job completed successfully
failed(job: Job<T>, error: Error)Job processing failed
progress(job: Job<T> | null, progress: number)Job progress updated
stalled(jobId: string, reason: string)Job stalled (no heartbeat)
drained()Queue has no more waiting jobs
error(error: Error)Worker-level error
cancelled({ jobId: string, reason: string })Job was cancelled
log(job: Job<T>, message: string)Log written via job.log()
closed()Worker shut down

For Bun Workers, completed and failed are broker-authoritative. The Worker emits the local terminal event only after the broker accepts that exact lease generation. If the broker’s processing timeout already failed or requeued the job while a processor was still returning, its late ACK/FAIL is an idempotent no-op and no contradictory local terminal event or Worker error is emitted. This rule is identical for Embedded, TCP, manual job.moveToFailed(), and SandboxedWorker execution.

The same ownership rule covers nonterminal processor mutations. After the broker accepts retry(), changeDelay(), moveToWait(), moveToDelayed(), moveToWaitingChildren(), or discard(), the Worker emits no local completed/failed event for the retired generation and sends no automatic ACK/FAIL. A real asynchronous discard() rejection emits exactly one Worker error; an authoritative already-absent result is silent.

For the Bun client, stalled is queue-scoped in both embedded and TCP mode. TCP workers receive it through a dedicated authenticated event connection that automatically re-subscribes after reconnect. skipStalledCheck: true disables only that listener, never broker-side stall recovery. The event is also emitted when an expired lock exhausts the queue’s retry or stall budget; in that case the broker records stalled before the terminal failed queue event and DLQ transition.

Remove a listener with worker.off('completed', handler).

WorkerCreate a worker and process your first job
Worker Concurrency and Batch PullingRun jobs in parallel and pull them in batches
The Job Object Inside a Worker ProcessorEverything the processor receives and can do
Worker Error Handling, Retries and BackoffRetries, backoff, timeouts and giving up
Worker LifecyclePause, resume and shut down without losing work
Heartbeats, Stall Detection and Lock OwnershipHeartbeats, stall recovery and lock ownership
SandboxedWorkerExperimental isolation for CPU-heavy handlers
WorkerOptions ReferenceEvery WorkerOptions field, with defaults