Skip to content

Events & Observability ​

Once a task is running, you'll often want to know what it's doing: when it ran, whether it succeeded, how long it took, whether a run was missed. node-cron exposes this through lifecycle events.

Every ScheduledTask, whether inline or background, supports .on(), .once(), and .off(). Each listener receives a TaskContext with metadata about the task and the specific execution.

js
import cron from 'node-cron';

const task = cron.schedule('* * * * *', async () => {
  return doWork();
});

task.on('execution:finished', (ctx) => {
  console.log(`done in ${ctx.execution?.finishedAt - ctx.execution?.startedAt}ms`);
});

task.on('execution:failed', (ctx) => {
  console.error('failed:', ctx.execution?.error?.message);
});

πŸ’‘ Attach listeners before the task starts to avoid missing early events. With cron.schedule the task starts immediately, so for guaranteed coverage create it stopped with cron.createTask, attach listeners, then call .start().

Available events ​

EventPayloadEmitted when…
task:startedTaskContextThe task is started via .start().
task:stoppedTaskContextThe task is stopped via .stop().
task:destroyedTaskContextThe task is destroyed via .destroy().
execution:startedTaskContextRight before the task function runs.
execution:finishedTaskContextThe task function finishes successfully.
execution:failedTaskContextThe task function throws or rejects.
execution:missedTaskContextA scheduled run was missed (blocking I/O or high CPU).
execution:overlapTaskContextA run was skipped because a previous one was still going (noOverlap).
execution:maxReachedTaskContextmaxExecutions was reached. The task is then destroyed.
execution:skippedTaskContextA distributed run was skipped on this instance. ctx.reason is 'not-elected' (another instance ran it) or 'coordinator-error' (the coordinator failed; failed closed).

Subscribing ​

js
import cron from 'node-cron';

const task = cron.createTask('* * * * *', async (ctx) => {
  console.log('running at', ctx.dateLocalIso);
  return 'done';
});

// React every time
task.on('execution:finished', (ctx) => {
  console.log('result:', ctx.execution?.result);
});

// React just once, then auto-remove
task.once('task:started', () => console.log('scheduler is up'));

// Stop listening
const onFail = (ctx) => console.error(ctx.execution?.error);
task.on('execution:failed', onFail);
task.off('execution:failed', onFail);

task.start();

TaskContext ​

Every event delivers a TaskContext, giving consistent access to timing and execution metadata.

ts
export type TaskContext = {
  date: Date;
  dateLocalIso: string;
  triggeredAt: Date;
  task?: ScheduledTask;
  execution?: Execution;
  reason?: 'not-elected' | 'coordinator-error';
};
FieldTypeDescription
dateDateThe time the run was scheduled for.
dateLocalIsostringHuman-readable local timestamp, using the task's timezone.
triggeredAtDateWhen the event was actually emitted. Useful for spotting drift.
taskScheduledTask?The task instance.
executionExecution?Details of the run (present for execution:* events).
reasonstring?Why a run was skipped. Present only on execution:skipped: 'not-elected' or 'coordinator-error'.

Execution ​

TaskContext.execution describes a single run of the task:

ts
export type Execution = {
  id: string;
  reason: 'invoked' | 'scheduled';
  startedAt?: Date;
  finishedAt?: Date;
  error?: Error;
  result?: any;
};
FieldTypeDescription
idstringUnique id for this execution.
reasonstring'scheduled' (fired by the schedule) or 'invoked' (via execute()).
startedAtDate?When the run started.
finishedAtDate?When the run finished.
errorError?The error, if the run failed.
resultany?The return value, if the run succeeded.

Notes ​

  • All listeners receive a TaskContext, even for non-execution events like task:stopped (where execution is absent).
  • Background tasks emit the same events with the same context, relayed from the worker process.
  • Listening to execution:missed also suppresses the default missed-execution warning, since node-cron assumes you're handling it. See Logging.

Next steps ​

  • Background Tasks: run jobs in isolated processes (same events apply).
  • Logging: route the warnings and errors behind these events through your own logger.

Released in 2016 under the ISC License.