Skip to content

lastDate()/lastExecution does not reflect the last execution of the registered callback(s) #1048

@MaSpeng

Description

@MaSpeng

Description

As originally described in the issue #710 the method lastDate() and therefore lastExecution indicate the moment when the last time was checked, if the next execution can be triggered and not when.

In the following section we are able to see that the lastExecution property will be set to the current Date before fireOnTick was even called.

node-cron/src/job.ts

Lines 336 to 344 in d6f7c4a

// we have arrived at the correct point in time.
this.lastExecution = new Date();
this._isActive = false;
// start before calling back so the callbacks have the ability to stop the cron job
if (!this.runOnce) this.start();
void this.fireOnTick();

I think this is a bug as the variable name, lastExecution, and method description lastDate: Provides the last execution date. indicate information about the last execution rather the last tick as it's otherwise indicated by the methods

nextDate: Indicates the subsequent date that will activate an onTick.
nextDates(count): Supplies an array of upcoming dates that will initiate an onTick.

I think currently we are seeing more likely the information about the lastTick or lastStart if you will.

Expected Behavior

lastDate() and lastExecution actually represent the moment when the callback(s) where invoked.

Actual Behavior

lastDate() and lastExecution represent the moment of the last check if the callback(s) can be invoked and will be updated on each iteration even without invoking any callback.

Possible Fix

Set lastExecution after setting _isCallbackRunning = true at:

this._isCallbackRunning = true;

Drop setting lastExecution at:

Steps to Reproduce

import { CronJob } from 'cron';

console.log('=== Issue: lastExecution changes while callback is running ===\n');
console.log('Cron: every 5 seconds');
console.log('Callback: takes 60 seconds');
console.log('waitForCompletion: true (should prevent overlapping executions)\n');

let callbackStartTime: Date | null = null;

const callback = async () => {
  callbackStartTime = new Date();
  console.log(`🔵 Callback STARTED at: ${callbackStartTime.toISOString()}\n`);
  await new Promise((resolve) => setTimeout(resolve, 60000)); // 60 seconds
  console.log(`🟢 Callback COMPLETED at: ${new Date().toISOString()}\n`);
  callbackStartTime = null;
};

const job = new CronJob('*/5 * * * * *', callback, null, true, null, null, false, null, false, true);

// Monitor every 2 seconds
setInterval(() => {
  const now = new Date();
  const actual = job.lastExecution;
  const isRunning = job.isCallbackRunning;

  if (!actual) return;

  console.log(`[${now.toISOString()}]`);
  console.log(`  isCallbackRunning: ${isRunning}`);
  console.log(`  ACTUAL   lastExecution: ${actual.toISOString()}`);

  if (callbackStartTime) {
    console.log(`  EXPECTED lastExecution: ${callbackStartTime.toISOString()}`);
    const diff = actual.getTime() - callbackStartTime.getTime();

    if (Math.abs(diff) > 100) {
      console.log(`  ❌ DIFFERENCE: ${diff}ms`);
      console.log('  ❌ lastExecution changed while callback is STILL RUNNING!');
    } else {
      console.log('  ✅ lastExecution matches callback start time');
    }
  }
  console.log('');
}, 2000);

setTimeout(() => {
  job.stop();
  console.log('\n=== Summary ===');
  console.log('Problem: lastExecution updates every 5s even though callback is still running');
  console.log('Expected: lastExecution should stay constant until callback completes');
  console.log('Solution: Set lastExecution in fireOnTick() after isCallbackRunning = true');
  process.exit(0);
}, 65000);

Context

We want to monitor our cron's and know if they maybe take longer as they should or they are not invoked anymore besides that the callback is not running.

At least for us it's no solution to work with the threshold and timeout to execute the callback(s) even if its still running.

Your Environment

  • cron version: 4.4.0
  • NodeJS version: 25.7.0
  • Operating System and version: Debian 13
  • TypeScript version (if applicable): 5.9.3
  • Link to your project (if applicable): not possible to provide, sorry

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions