Skip to content

fix: bypass empty-heartbeat-file check for cron jobs#4256

Closed
radling005 wants to merge 2 commits intoopenclaw:mainfrom
radling005:fix/cron-heartbeat-empty-file-bypass
Closed

fix: bypass empty-heartbeat-file check for cron jobs#4256
radling005 wants to merge 2 commits intoopenclaw:mainfrom
radling005:fix/cron-heartbeat-empty-file-bypass

Conversation

@radling005
Copy link

Summary

Cron jobs with wakeMode: 'now' were being skipped when HEARTBEAT.md was empty or contained only comments, even though they had pending system events to process.

Problem

When a cron job fires with wakeMode: 'now', it:

  1. Enqueues the system event via enqueueSystemEvent(text)
  2. Calls runHeartbeatOnce({ reason: 'cron:${job.id}' })

But runHeartbeatOnce() checks if HEARTBEAT.md is effectively empty and skips if so — returning { status: 'skipped', reason: 'empty-heartbeat-file' }.

The bypass only existed for reason === 'exec-event', not for cron jobs.

Fix

Add cron jobs to the bypass list:

const isCronReason = opts.reason?.startsWith('cron:');

This ensures cron-triggered wakes are treated the same as exec-event wakes — both have pending system events that need processing regardless of HEARTBEAT.md state.

Testing

Tested locally with:

  • Cron job with wakeMode: 'now' and empty HEARTBEAT.md
  • Before: lastStatus: 'skipped', lastError: 'empty-heartbeat-file'
  • After: Job executes and delivers as expected

Fixes #4224

Cron jobs with wakeMode: 'now' were being skipped when HEARTBEAT.md
was empty, even though they had pending system events to process.

This adds cron jobs to the bypass list alongside exec events, since
both have the same need: they've already enqueued system events and
need the agent to wake up and process them.

Fixes #4224
@jeanlucthumm
Copy link

Thanks, thats what I had in mind too

@josecfreittas
Copy link

+1

@radling005 radling005 closed this by deleting the head repository Jan 31, 2026
radling005 pushed a commit to radling005/openclaw that referenced this pull request Feb 1, 2026
## Summary

Two related fixes for cron job reliability:

1. **Empty HEARTBEAT.md bypass for cron jobs** (PR openclaw#4256 issue)
   - Cron jobs with `wakeMode: 'now'` were being skipped when HEARTBEAT.md
     was empty or contained only comments
   - Added `isCronReason` check alongside existing `isExecEventReason` bypass

2. **Prevent infinite retry loop for one-shot jobs**
   - Discovered while testing fix openclaw#1
   - One-shot 'at' jobs that got skipped (e.g., quiet-hours) would
     immediately retry because `nextRunAtMs` remained set to the past time
   - Fix: Disable 'at' jobs after any attempt (ok, skipped, or error)

## Problem

When a cron job fires with `wakeMode: 'now'`:
1. Enqueues system event via `enqueueSystemEvent()`
2. Calls `runHeartbeatOnce({ reason: 'cron:${job.id}' })`

The heartbeat runner was checking if HEARTBEAT.md was empty and skipping,
even though the cron job had already enqueued a system event to process.

Additionally, for one-shot 'at' jobs that were skipped:
- `computeJobNextRunAtMs()` returned the same past `atMs` time
- Timer re-armed immediately, creating an infinite loop
- Flood of cron events (1400+ per second observed)

## Testing

- Verified cron job fires despite empty HEARTBEAT.md ✓
- Verified one-shot job disables after being skipped ✓
- All existing tests pass (cron: 52/52, heartbeat: 52/52)

Fixes openclaw#4224
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reminders not working: blocked when heartbeat disabled

3 participants

Comments