openclaw - ✅(Solved) Fix Heartbeat activeHours timezone ignored — phase schedules based on UTC, not configured timezone [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#75487Fetched 2026-05-02 05:34:00
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
2
Author
Timeline (top)
cross-referenced ×2mentioned ×2subscribed ×2commented ×1

Fix Action

Fixed

PR fix notes

PR #75597: fix(heartbeat): make phase scheduling active-hours-aware (#75487)

Description (problem / solution / changelog)

Summary

Fixes #75487.

computeNextHeartbeatPhaseDueMs computes next-fire times using pure UTC-epoch modular arithmetic. activeHours was only checked as a runtime execution guard in runHeartbeatOnce — when a quiet-hours slot was skipped, advanceAgentSchedule computed the next slot (also in UTC) which could also be in quiet hours.

This caused:

  • Wasted quiet-hours ticks (skipped at runtime but still consumed/advanced)
  • Long dormant gaps after gateway restarts during quiet hours
  • With non-UTC timezones like Asia/Shanghai, multiple consecutive phase slots falling outside the active window before the next in-window slot

Changes

src/infra/heartbeat-schedule.ts

  • Added seekNextActivePhaseDueMs() — takes a starting slot and an isActive predicate, iterates forward through phase-aligned slots (up to a 7-day horizon), and returns the first in-window slot. Falls back to the raw slot if no active slot is found within the horizon (so the runtime guard can still gate execution).

src/infra/heartbeat-runner.ts

  • advanceAgentSchedule now wraps the computed next-due time with seekNextActivePhaseDueMs, using isWithinActiveHours as the predicate.
  • updateConfig initial scheduling also seeks forward to the first in-window slot on startup/config reload.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/infra/heartbeat-runner.active-hours-schedule.e2e.test.ts (added, +248/-0)
  • src/infra/heartbeat-runner.ts (modified, +36/-2)
  • src/infra/heartbeat-schedule.test.ts (modified, +166/-0)
  • src/infra/heartbeat-schedule.ts (modified, +38/-0)

Code Example

"heartbeat": {
  "every": "4h",
  "activeHours": {
    "start": "08:00",
    "end": "23:00",
    "timezone": "Asia/Shanghai"
  }
}

---

2026-05-01T00:56:12.947+08:00 [heartbeat] started
00:56 上海,属于 quiet hours,但调度器只记录"started"不说明下一次触发时间)
RAW_BUFFERClick to expand / collapse

Heartbeat activeHours ignores configured timezone — schedules based on UTC phase instead

Heartbeat 配置了 activeHours 时区为 Asia/Shanghai,但调度逻辑完全按 UTC phase 对齐,activeHours 的时区和窗口被忽略。

配置

"heartbeat": {
  "every": "4h",
  "activeHours": {
    "start": "08:00",
    "end": "23:00",
    "timezone": "Asia/Shanghai"
  }
}

问题

Phase 基于 UTC 计算,heartbeat 在以下 UTC 时刻触发:

  • 03:21 UTC → 11:21 Shanghai ✅ (在 activeHours 内)
  • 07:21 UTC → 15:21 Shanghai ✅ (在 activeHours 内)
  • 11:21 UTC → 19:21 Shanghai ✅ (在 activeHours 内)
  • 15:21 UTC → 23:21 Shanghai ❌ (quiet)
  • 19:21 UTC → 03:21 Shanghai ❌ (quiet)
  • 23:21 UTC → 07:21 Shanghai ❌ (quiet)

当 Gateway 在 quiet hours 时间窗口重启后,下一个 phase 触发时间可能仍然落在 quiet hours,导致心跳长时间不执行。

预期行为

activeHours 的 timezone 和窗口应该决定心跳应该在哪个本地时间触发,而不是"触发后决定是否执行"。

建议修复

方案 A:在计算下次触发时间时考虑 activeHours 窗口,从当前时刻开始找下一个落在 activeHours 内的 phase 对齐时间点

方案 B:允许用户配置 phase 对齐到本地时区的某个固定时间(如"每天 12:00 上海执行"),而不是 UTC 随机偏移

方案 C:添加启动时立即触发一次的功能(如果当前在 activeHours 内),避免静默等待

日志证据

2026-05-01T00:56:12.947+08:00 [heartbeat] started
(00:56 上海,属于 quiet hours,但调度器只记录"started"不说明下一次触发时间)

extent analysis

TL;DR

Consider adjusting the heartbeat scheduling logic to account for the configured timezone, ensuring that the active hours window is respected.

Guidance

  • Review the scheduling algorithm to ensure it correctly handles timezone conversions and active hours configurations.
  • Verify that the activeHours configuration is being properly parsed and applied to the scheduling logic.
  • Investigate implementing a solution similar to Scheme A, which considers the active hours window when calculating the next trigger time.
  • Test the heartbeat functionality with different timezone configurations to ensure the issue is resolved.

Example

No code snippet is provided as the issue lacks specific implementation details.

Notes

The provided information suggests a timezone-related issue, but the exact implementation details are unclear. A thorough review of the scheduling logic and timezone handling is necessary to resolve the issue.

Recommendation

Apply workaround: Implement a solution similar to Scheme A, which considers the active hours window when calculating the next trigger time, to ensure the heartbeat respects the configured timezone and active hours.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Heartbeat activeHours timezone ignored — phase schedules based on UTC, not configured timezone [1 pull requests, 1 comments, 2 participants]