openclaw - ✅(Solved) Fix Heartbeat does not reload HEARTBEAT.md after gateway restart [1 pull requests, 3 comments, 3 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#51542Fetched 2026-04-08 01:09:49
View on GitHub
Comments
3
Participants
3
Timeline
11
Reactions
1
Author
Timeline (top)
commented ×3mentioned ×2subscribed ×2closed ×1

Root Cause

Root Cause Analysis

Fix Action

Workaround

No reliable workaround currently exists.

Possible workarounds (unverified):

  1. Clear all session state (if there is a cache directory)
  2. Modify openclaw.json to force re-initialization
  3. Wait for heartbeat session to naturally expire (if there is a time limit)

PR fix notes

PR #52165: fix(heartbeat): keep heartbeat turns out of session transcript

Description (problem / solution / changelog)

Summary

Heartbeat runs usually share the main session unless isolatedSession is enabled. That means a successful heartbeat can leave its own prose behind in the shared transcript, and later heartbeat runs may keep getting steered by that old heartbeat conversation even after HEARTBEAT.md has changed. In the reported restart case, the file itself was available, but the retained heartbeat transcript was still competing with the new instructions.

This change keeps heartbeat traffic out of the durable shared session transcript after successful or intentionally skipped heartbeat runs. The runner now prunes heartbeat turns not only for the existing HEARTBEAT_OK / empty / duplicate paths, but also after the normal send path and the other no-op delivery paths. That lets the latest HEARTBEAT.md guidance win on the next run without resetting the user's main session or throwing away non-heartbeat conversation history.

Testing

  • pnpm exec vitest run src/infra/heartbeat-runner.transcript-prune.test.ts src/infra/heartbeat-runner.model-override.test.ts src/infra/heartbeat-runner.respects-ackmaxchars-heartbeat-acks.test.ts src/infra/heartbeat-runner.returns-default-unset.test.ts

Closes #51542.

Changed files

  • src/infra/heartbeat-runner.transcript-prune.test.ts (modified, +2/-2)
  • src/infra/heartbeat-runner.ts (modified, +4/-0)

Code Example

**[Final Output Format Requirements]**
If no urgent matters are found, reply exactly: `HEARTBEAT_OK` at the beginning of your message.

 **Incorrect Example**:

📧 No new emails
🔍 No failed commands
HEARTBEAT_OK


**Correct Example**:

HEARTBEAT_OK

📧 No new emails
🔍 No failed commands

---

Another heartbeat check at 1:38 PM, 30 minutes after the last one. Everything remains unchanged - no new emails, no failures.
HEARTBEAT_OK

**巡检时间**: 2026-03-21 13:38 (Asia/Shanghai)
**📧 邮件检查**
- 无新邮件
**🔍 对话复盘**
- 过去 1 小时无失败命令
- 无需要记录的纠正或改进
**💬 互动间隔**
-30 分钟前有互动
- 无需问候
**状态**: ✅ 一切正常

---

# HEARTBEAT.mdMonitoring Task List

> ⚠️ **System-level Interception Rule**:
> If there are no urgent matters requiring immediate user attention, the system must silently intercept the reply to avoid disturbing the user.
> **The absolute technical condition for interception to take effect: `HEARTBEAT_OK` must appear on the [last line] of your entire reply content, and there must be absolutely no characters, punctuation, newlines, or status summaries after it.**

## 📋 Required Tasks for Each Heartbeat

1. **Email Check**Run `himalaya envelope list`, only report if there are [family] or [work-urgent] related new emails
2. **Conversation Review**Quickly review the past 1 hour of conversation: any failed commands or methods that need improvement? If yes, record to daily memory
3. **8-Hour No Interaction**Carefully check context. Only send a simple greeting if it has been more than 8 hours since the last message from [human user]

---

**[Final Output Format Requirements]**
If no urgent matters are found, you can output your Chinese/English inspection briefing first, but **the last line of the entire reply must end precisely with `HEARTBEAT_OK`**.

 **Incorrect Example** (never put the keyword in the middle):

Another heartbeat check...
HEARTBEAT_OK
Inspection time: 13:38


**Correct Example** (briefing first, last line收尾):

Inspection time: 13:38
📧 No new emails
🔍 No failed commands in the past 1 hour
💬 No greeting needed
Status: All normal
HEARTBEAT_OK

---

# 1. Unload LaunchAgent
launchctl bootout gui/$UID/ai.openclaw.gateway

# 2. Remove plist
rm ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# 3. Kill process (if still running)
kill -9 <PID>

# 4. Start again
openclaw gateway start

---

Inspection time: 17:41
📧 No new emails
🔍 No failed commands in the past 1 hour
💬 No greeting needed
Status: All normal
HEARTBEAT_OK

---

🌙 [Title]

**Summary**:
- Key point 1
- Key point 2

**Status**:Complete /In Progress
📄 Full report: `file path.md`
HEARTBEAT_OK

---

**Inspection Time**: 2026-03-21 17:41 (Asia/Shanghai)
📧 Email Check
- No new emails
🔍 Conversation Review
- No failed commands in the past 1 hour
- Gateway restart message sent
💬 Interaction Interval
- Interaction a few minutes ago
- No greeting needed
**Status**:All normal

---

Gateway starts
Reads HEARTBEAT.md content
Caches to heartbeat session prompt
Heartbeat session reuses cached prompt

---

Heartbeat is a long-running cron session
Reads HEARTBEAT.md on first start
Session state persists (not cleared on gateway restart)
Subsequent heartbeats reuse the same session state
RAW_BUFFERClick to expand / collapse

Labels: bug, heartbeat, gateway


Background

We initially configured HEARTBEAT.md to require HEARTBEAT_OK at the beginning of the heartbeat message. However, this caused persistent errors where the heartbeat system failed to recognize the acknowledgment properly.

Original HEARTBEAT.md requirement (causing errors):

**[Final Output Format Requirements]**
If no urgent matters are found, reply exactly: `HEARTBEAT_OK` at the beginning of your message.

**Incorrect Example**:

📧 No new emails
🔍 No failed commands
HEARTBEAT_OK


**Correct Example**:

HEARTBEAT_OK

📧 No new emails
🔍 No failed commands

Observed Errors (with HEARTBEAT_OK at beginning):

Actual heartbeat message received (2026-03-21 13:38, before HEARTBEAT.md update):

Another heartbeat check at 1:38 PM, 30 minutes after the last one. Everything remains unchanged - no new emails, no failures.
HEARTBEAT_OK

**巡检时间**: 2026-03-21 13:38 (Asia/Shanghai)
**📧 邮件检查**
- 无新邮件
**🔍 对话复盘**
- 过去 1 小时无失败命令
- 无需要记录的纠正或改进
**💬 互动间隔**
- 约 30 分钟前有互动
- 无需问候
**状态**: ✅ 一切正常

Problem: HEARTBEAT_OK appears in the middle of the message (after the intro sentence, before the inspection details), not at the beginning as required. This caused the heartbeat acknowledgment to not be properly recognized by the system.

To resolve this, we modified HEARTBEAT.md to require HEARTBEAT_OK at the end (last line) of the message. However, after making this change and performing a complete gateway restart, we discovered that the heartbeat content did not change at all — it continued using the old prompt format.


Problem Description

After modifying HEARTBEAT.md, the heartbeat session continues to use the old prompt even after a complete gateway restart. The new HEARTBEAT.md content is not dynamically loaded.

Specific Symptom:

  • Modified HEARTBEAT.md to require HEARTBEAT_OK on the last line of the message
  • After complete gateway restart, heartbeat still sends HEARTBEAT_OK in the middle of the message, not on the last line
  • Heartbeat message format remained completely unchanged despite the config modification

Environment

  • OpenClaw Version: v2026.3.13+ (to be confirmed)
  • OS: macOS 15.3 (Darwin 25.3.0, arm64)
  • Node Version: v25.8.1
  • Gateway Mode: LaunchAgent (managed by launchctl)
  • Heartbeat Config: 30-minute interval, Feishu DM target

Reproduction Steps

1. Modify HEARTBEAT.md

# HEARTBEAT.md — Monitoring Task List

> ⚠️ **System-level Interception Rule**:
> If there are no urgent matters requiring immediate user attention, the system must silently intercept the reply to avoid disturbing the user.
> **The absolute technical condition for interception to take effect: `HEARTBEAT_OK` must appear on the [last line] of your entire reply content, and there must be absolutely no characters, punctuation, newlines, or status summaries after it.**

## 📋 Required Tasks for Each Heartbeat

1. **Email Check** — Run `himalaya envelope list`, only report if there are [family] or [work-urgent] related new emails
2. **Conversation Review** — Quickly review the past 1 hour of conversation: any failed commands or methods that need improvement? If yes, record to daily memory
3. **8-Hour No Interaction** — Carefully check context. Only send a simple greeting if it has been more than 8 hours since the last message from [human user]

---

**[Final Output Format Requirements]**
If no urgent matters are found, you can output your Chinese/English inspection briefing first, but **the last line of the entire reply must end precisely with `HEARTBEAT_OK`**.

**Incorrect Example** (never put the keyword in the middle):

Another heartbeat check...
HEARTBEAT_OK
Inspection time: 13:38


**Correct Example** (briefing first, last line收尾):

Inspection time: 13:38
📧 No new emails
🔍 No failed commands in the past 1 hour
💬 No greeting needed
Status: All normal
HEARTBEAT_OK

Modification Time: 2026-03-21 13:50:58 (Asia/Shanghai)

2. Restart Gateway

# 1. Unload LaunchAgent
launchctl bootout gui/$UID/ai.openclaw.gateway

# 2. Remove plist
rm ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# 3. Kill process (if still running)
kill -9 <PID>

# 4. Start again
openclaw gateway start

3. Observe Heartbeat Output

Wait for the next heartbeat trigger (30-minute interval) and observe the output format.

Expected Result:

Inspection time: 17:41
📧 No new emails
🔍 No failed commands in the past 1 hour
💬 No greeting needed
Status: All normal
HEARTBEAT_OK

Actual Result:

🌙 [Title]

**Summary**:
- Key point 1
- Key point 2

**Status**: ✅ Complete / ⏳ In Progress
📄 Full report: `file path.md`
HEARTBEAT_OK

---

**Inspection Time**: 2026-03-21 17:41 (Asia/Shanghai)
📧 Email Check
- No new emails
🔍 Conversation Review
- No failed commands in the past 1 hour
- Gateway restart message sent
💬 Interaction Interval
- Interaction a few minutes ago
- No greeting needed
**Status**: ✅ All normal

Problem: HEARTBEAT_OK appears in the middle of the message, not on the last line.


Timeline Investigation

TimeEventDescription
2026-03-17 19:22Gateway startedPID 78440
2026-03-21 13:50HEARTBEAT.md modified725 bytes, added last-line requirement
2026-03-21 13:53Config hot reloadackMaxChars=2000
2026-03-21 14:13Heartbeat startedgateway.log entry
2026-03-21 16:16Heartbeat startedgateway.log entry
2026-03-21 17:01Discovered gateway not restartedPID 78440 running since Mar 17
2026-03-21 17:11Complete gateway restartlaunchctl bootout + kill -9
2026-03-21 17:13Gateway new processPID 84268
2026-03-21 17:41Heartbeat triggeredStill using old prompt

Root Cause Analysis

Hypothesis 1: Gateway caches HEARTBEAT.md at startup

Speculated Mechanism:

Gateway starts
Reads HEARTBEAT.md content
Caches to heartbeat session prompt
Heartbeat session reuses cached prompt

Evidence:

  • Gateway ran from Mar 17 to Mar 21 (4 days), during which HEARTBEAT.md was modified
  • Config hot reload only updates partial config (e.g., ackMaxChars), does not re-read HEARTBEAT.md
  • Problem persists after complete gateway restart

Contradiction:

  • If gateway reads HEARTBEAT.md at startup, it should read new content after restart
  • But problem persists after restart (new PID 84268)

Hypothesis 2: Heartbeat Session has independent cache

Speculated Mechanism:

Heartbeat is a long-running cron session
Reads HEARTBEAT.md on first start
Session state persists (not cleared on gateway restart)
Subsequent heartbeats reuse the same session state

Evidence:

  • gateway.log shows heartbeat sessions have independent session IDs
  • Heartbeat sessions might be "persistent", not recreated on each trigger

To Confirm:

  • Are heartbeat sessions really persistent?
  • Where is session state stored?

Hypothesis 3: HEARTBEAT.md read path issue

To Confirm:

  • Which HEARTBEAT.md does heartbeat actually read?
  • Is it workspace/HEARTBEAT.md or another path?
  • Are there multiple HEARTBEAT.md files?

Confirmed:

  • ~/.openclaw/workspace/HEARTBEAT.md exists (1593 bytes, modified 2026-03-21 13:50)
  • ~/.openclaw/HEARTBEAT.md does not exist
  • OpenClaw docs state: HEARTBEAT.md defaults to agent workspace

Attempted Solutions

SolutionOperationResult
Config hot reloadopenclaw config set agents.defaults.heartbeat.ackMaxChars 2000❌ Invalid (only updates config, does not re-read HEARTBEAT.md)
Complete gateway restartlaunchctl bootout + kill -9 + openclaw gateway start❌ Invalid (new PID 84268, problem persists)
Confirm file pathCheck ~/.openclaw/workspace/HEARTBEAT.md exists and content is correct✅ File is correct, but not being read

Open Questions

  1. Heartbeat prompt loading timing:

    • Is it loaded once at gateway startup?
    • Or dynamically read on each heartbeat trigger?
    • Or is there another caching mechanism?
  2. Heartbeat session lifecycle:

    • Is heartbeat session created new on each trigger, or is it a long-running session?
    • Is session state persisted? Where is it stored?
  3. HEARTBEAT.md read path:

    • Is there a config file that specifies the HEARTBEAT.md path?
    • Is hot reload supported?

Expected Behavior

  1. Dynamic Read: Heartbeat should read the latest HEARTBEAT.md content on each trigger
  2. Or Support Reload: At minimum, should re-read HEARTBEAT.md after gateway restart
  3. Or Document: If this is by design, clearly document how to refresh heartbeat prompt

Workaround

No reliable workaround currently exists.

Possible workarounds (unverified):

  1. Clear all session state (if there is a cache directory)
  2. Modify openclaw.json to force re-initialization
  3. Wait for heartbeat session to naturally expire (if there is a time limit)

Impact

  • Severity: Medium (affects heartbeat behavior customization, but does not affect core functionality)
  • Affected Users: All users who customize HEARTBEAT.md
  • Trigger Condition: After modifying HEARTBEAT.md, expecting changes to take effect

Suggested Fix Directions

  1. Dynamic Read: Read HEARTBEAT.md on each heartbeat trigger (recommended)
  2. Session Reset: Force reset all heartbeat sessions on gateway restart
  3. Cache Invalidation: Detect HEARTBEAT.md file changes, automatically invalidate cache
  4. Documentation: If this is by design, clearly document how to refresh heartbeat prompt

extent analysis

Fix Plan

To resolve the issue of the heartbeat content not changing after modifying HEARTBEAT.md, we need to ensure that the heartbeat system dynamically reads the latest HEARTBEAT.md content on each trigger or supports a reload mechanism after gateway restart. Here are the steps:

  • Dynamic Read: Modify the heartbeat system to read HEARTBEAT.md on each trigger. This can be achieved by:
    • Checking the file modification time of HEARTBEAT.md before each heartbeat trigger.
    • If the file has been modified, reload the content.
  • Session Reset: Force reset all heartbeat sessions on gateway restart. This can be done by:
    • Clearing the session cache directory (if exists).
    • Modifying the openclaw.json file to force re-initialization.
  • Cache Invalidation: Detect HEARTBEAT.md file changes and automatically invalidate the cache. This can be achieved by:
    • Implementing a file watcher to monitor changes to HEARTBEAT.md.
    • Invalidating the cache when changes are detected.

Example code snippet to dynamically read HEARTBEAT.md on each heartbeat trigger:

import os

def read_heartbeat_md():
    heartbeat_md_path = '~/.openclaw/workspace/HEARTBEAT.md'
    with open(heartbeat_md_path, 'r') as f:
        return f.read()

def check_heartbeat_md_modification():
    heartbeat_md_path = '~/.openclaw/workspace/HEARTBEAT.md'
    current_modification_time = os.path.getmtime(heartbeat_md_path)
    # Store the current modification time for the next check
    return current_modification_time

def heartbeat_trigger():
    # Check if HEARTBEAT.md has been modified
    current_modification_time = check_heartbeat_md_modification()
    if current_modification_time != previous_modification_time:
        # Reload HEARTBEAT.md content
        heartbeat_md_content = read_heartbeat_md()
        # Update the heartbeat prompt
        update_heartbeat_prompt(heartbeat_md_content)
    # Trigger the heartbeat
    trigger_heartbeat()

Verification

To verify that the fix worked, you can:

  • Modify HEARTBEAT.md and check if the changes are reflected in the heartbeat content after the next trigger.
  • Check the gateway logs to ensure that the heartbeat system is reading the latest HEARTBEAT.md content.

Extra Tips

  • Ensure that the HEARTBEAT.md file is properly formatted and follows the required format.
  • Consider implementing a caching mechanism with a time-to-live (TTL) to reduce the number of times HEARTBEAT.md is read.
  • Document the fix and the expected behavior to avoid similar issues in the future.

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 does not reload HEARTBEAT.md after gateway restart [1 pull requests, 3 comments, 3 participants]