Skip to content

Fixes cron jobs#3335

Open
hkirat wants to merge 2 commits intoopenclaw:mainfrom
hkirat:cron-fix
Open

Fixes cron jobs#3335
hkirat wants to merge 2 commits intoopenclaw:mainfrom
hkirat:cron-fix

Conversation

@hkirat
Copy link

@hkirat hkirat commented Jan 28, 2026

Fixes #2323

Cron jobs seems to have broken after #1535

The reason seems to be an early return that was added in the PR above, that would not the system events queue clear if HEARTBEAT.md is empty.

Cron jobs are pushed to system events when executed, and hence the change ensures that if there are pending system events they should be executed irrespective of the heartbeat.md file being empty.

Greptile Overview

Greptile Summary

This PR adjusts the heartbeat runner’s “empty HEARTBEAT.md” early-return behavior so that pending system events (e.g., cron-triggered reminders queued into the per-session system-events queue) still cause a heartbeat run. It adds tests covering the new behavior (run when HEARTBEAT.md is effectively empty but system events exist; still skip when empty and no events).

The change integrates with existing heartbeat logic by checking the session’s system-events queue before applying the cost-saving skip, ensuring cron/system-event delivery isn’t blocked by an empty heartbeat file.

Confidence Score: 4/5

  • This PR is likely safe to merge and addresses the reported cron/system-events regression with targeted tests.
  • The functional change is small and well-scoped (only gating the empty-heartbeat skip on presence of queued system events) and is covered by new tests. Main remaining risk is around edge cases where sessionKey resolution could be invalid and hasSystemEvents may throw earlier than before.
  • src/infra/heartbeat-runner.ts (sessionKey/system-events check edge cases)

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

@hkirat
Copy link
Author

hkirat commented Jan 28, 2026

Tried inside the terminal and telegram.
Screenshot below from tg. Wasn't working before these changes.
Screenshot 2026-01-28 at 6 49 28 PM

@Glucksberg
Copy link
Contributor

This PR may help with several open cron issues:

Issues this could address:

Related PRs:

Could you clarify which specific cron bugs this addresses? Would help with issue triage.

@hkirat
Copy link
Author

hkirat commented Jan 29, 2026

This one specifically solves #2323 and potentially #2813

It should not affect #3220 and #3486

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +463 to 470
const { entry, sessionKey, storePath } = resolveHeartbeatSession(cfg, agentId, heartbeat);

// Skip heartbeat if HEARTBEAT.md exists but has no actionable content.
// This saves API calls/costs when the file is effectively empty (only comments/headers).
// EXCEPTION: Don't skip for exec events - they have pending system events to process.
// EXCEPTION: Don't skip for exec events or if there are pending system events (from cron, etc.)
const isExecEventReason = opts.reason === "exec-event";
const hasPendingSystemEvents = hasSystemEvents(sessionKey);
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] hasSystemEvents(sessionKey) can throw before the heartbeat-file check

hasSystemEvents calls requireSessionKey and will throw if resolveHeartbeatSession returns an empty/invalid sessionKey. Previously the empty-heartbeat-file optimization would just try to read HEARTBEAT.md and could skip cleanly; with this change an invalid/misconfigured session key can now crash the runner before it gets to the try/catch around reading the file.

If this can happen (e.g., malformed heartbeat.session config), consider guarding the check or treating invalid session keys as “no pending events” so heartbeat runner can still proceed/skip predictably.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/heartbeat-runner.ts
Line: 463:470

Comment:
[P1] `hasSystemEvents(sessionKey)` can throw before the heartbeat-file check

`hasSystemEvents` calls `requireSessionKey` and will throw if `resolveHeartbeatSession` returns an empty/invalid `sessionKey`. Previously the `empty-heartbeat-file` optimization would just try to read `HEARTBEAT.md` and could skip cleanly; with this change an invalid/misconfigured session key can now crash the runner before it gets to the try/catch around reading the file.

If this can happen (e.g., malformed `heartbeat.session` config), consider guarding the check or treating invalid session keys as “no pending events” so heartbeat runner can still proceed/skip predictably.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1046 to +1054
// Enqueue a system event (simulates cron job)
resetSystemEventsForTest();
enqueueSystemEvent("⏰ Reminder: Check your tasks", { sessionKey });

replySpy.mockResolvedValue([{ text: "Got your reminder! Checking tasks now." }]);
const sendWhatsApp = vi.fn().mockResolvedValue({
messageId: "m1",
toJid: "jid",
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Test enqueues a system event but doesn’t assert it was actually consumed

This test verifies the heartbeat runs when events exist, but it doesn’t check that the system-event queue is drained/cleared. A regression where events remain pending (or are processed repeatedly) would still pass. It may be worth asserting queue size/hasSystemEvents(sessionKey) becomes false after runHeartbeatOnce if that’s the intended behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/heartbeat-runner.returns-default-unset.test.ts
Line: 1046:1054

Comment:
[P2] Test enqueues a system event but doesn’t assert it was actually consumed

This test verifies the heartbeat runs when events exist, but it doesn’t check that the system-event queue is drained/cleared. A regression where events remain pending (or are processed repeatedly) would still pass. It may be worth asserting queue size/`hasSystemEvents(sessionKey)` becomes false after `runHeartbeatOnce` if that’s the intended behavior.

How can I resolve this? If you propose a fix, please make it concise.

@adasowa
Copy link

adasowa commented Feb 3, 2026

Confirming this fix works! 🎉

I've been running OpenClaw from source with this PR (along with #7077 and #7230) and my cron jobs are now firing correctly.

Specifically, I had cron jobs that were silently failing because:

  1. HEARTBEAT.md was empty (just comments)
  2. Cron was pushing system events
  3. The heartbeat runner was returning early before processing those events

With this fix applied, cron-triggered system events are properly delivered even when HEARTBEAT.md is empty. Tested with both wakeMode: now and wakeMode: next-heartbeat — both work as expected.

Thanks for tracking this down!

@Takhoffman
Copy link
Contributor

Hello, we have had a lot of cron fixes go out. Are you still having issues?

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.

[Bug]: Cronjob with whatsapp does not work

4 participants

Comments