Job context
Every handler receives a JobContext as its last argument. The worker builds
it per execution — you never construct it yourself.
async handle(payload, ctx) {
ctx.logger.info("starting", { jobId: ctx.jobId });
ctx.reportProgress(0.5);
await doWork(payload, { signal: ctx.signal });
}
Fields
| Member | Type | Description |
|---|---|---|
jobId | string | Unique identifier for this execution. Use it as the idempotency key for side effects. |
jobType | string | The job type this execution was routed to. |
attempt | number | Which attempt this is (1-based). |
maxAttempts | number | The maximum attempts configured for the job. |
enqueuedAt | Date | When the job was originally enqueued. |
payload | T | The deserialized payload (undefined for recurring jobs). |
signal | AbortSignal | Aborts on timeout, host shutdown, or a server cancel. |
logger | Logger | A logger pre-bound with this job's id, type, and attempt. |
reportProgress(n) | (progress: number) => void | Report progress in [0, 1]. |
The cancellation signal
ctx.signal is the single source of cancellation. It aborts when:
- the job's
timeoutSecondselapses, - the worker is shutting down (SIGTERM / SIGINT), or
- a liveness check returns a cancel signal from the server.
Cancellation is cooperative. It takes effect only when your handler reacts to the signal. The two patterns:
// 1. Hand it to anything that accepts an AbortSignal.
await fetch(url, { signal: ctx.signal });
// 2. Check it in long loops.
for (const item of items) {
if (ctx.signal.aborted) break;
await process(item);
}
A handler that never observes the signal runs to completion regardless — there is no way to forcibly interrupt arbitrary JavaScript.
Reporting progress
reportProgress records a value the dashboard surfaces while the job runs. The
latest reported value rides the next liveness check.
- Values are clamped: anything above
1becomes1. - Values of
0or below,NaN, and non-finite values are ignored (progress stays unreported until you send a valid value). - It is last-write-wins — the most recent value is the one reported.
async handle(payload, ctx) {
const total = payload.items.length;
for (let i = 0; i < total; i++) {
await process(payload.items[i], { signal: ctx.signal });
ctx.reportProgress((i + 1) / total);
}
}
Logging
ctx.logger is pre-bound with the job's jobId, jobType, and attempt, so
every line you log inside a handler is automatically correlated to the job. It
implements a small structural interface (debug / info / warn / error),
which console satisfies. The worker never logs raw payloads or credentials —
log only what you need.
See also
- Defining jobs — the handler contract.
- Configuration — supplying a logger to the worker.