Skip to main content

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

MemberTypeDescription
jobIdstringUnique identifier for this execution. Use it as the idempotency key for side effects.
jobTypestringThe job type this execution was routed to.
attemptnumberWhich attempt this is (1-based).
maxAttemptsnumberThe maximum attempts configured for the job.
enqueuedAtDateWhen the job was originally enqueued.
payloadTThe deserialized payload (undefined for recurring jobs).
signalAbortSignalAborts on timeout, host shutdown, or a server cancel.
loggerLoggerA logger pre-bound with this job's id, type, and attempt.
reportProgress(n)(progress: number) => voidReport progress in [0, 1].

The cancellation signal

ctx.signal is the single source of cancellation. It aborts when:

  • the job's timeoutSeconds elapses,
  • 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 1 becomes 1.
  • Values of 0 or 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