Skip to content

fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK#32131

Merged
steipete merged 2 commits intoopenclaw:mainfrom
adhishthite:fix/cron-heartbeat-ok-multi-payload-delivery
Mar 2, 2026
Merged

fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK#32131
steipete merged 2 commits intoopenclaw:mainfrom
adhishthite:fix/cron-heartbeat-ok-multi-payload-delivery

Conversation

@adhishthite
Copy link
Contributor

Summary

Fixes delivery suppression for cron agents that return multiple text payloads before a final HEARTBEAT_OK.

Problem

isHeartbeatOnlyResponse() uses .every() to check if ALL payloads are heartbeat tokens. When agents narrate their work (reading files, checking emails) across multiple assistant text blocks before concluding with HEARTBEAT_OK, the check fails because narration payloads are not heartbeat tokens. Result: all payloads get delivered to the user as announce messages.

Real-world transcript from a heartbeat-30m cron run:

Payload 1: "It's 12:49 AM — quiet hours. Let me run the checks quickly."
Payload 2: "Emails: Just 2 calendar invites from Adnan. Not urgent at 12:49 AM."
Payload 3: "HEARTBEAT_OK"

Fix

Change isHeartbeatOnlyResponse() from .every() to .some():

  • If any payload contains a heartbeat ack token → suppress delivery (agent signals nothing needs attention)
  • If any payload has media → always deliver (preserve existing media delivery behavior)

The media check is lifted to a separate .some() pass for clarity.

Tests

Added 7 unit tests for isHeartbeatOnlyResponse in helpers.test.ts:

  • Empty payloads → suppressed
  • Single HEARTBEAT_OK → suppressed
  • Single non-heartbeat → not suppressed
  • Multi-payload with narration + HEARTBEAT_OK → suppressed (the bug case)
  • Media present → not suppressed (even with HEARTBEAT_OK text)
  • Media in different payload than HEARTBEAT_OK → not suppressed
  • No HEARTBEAT_OK anywhere → not suppressed

All 20 tests in helpers.test.ts pass. Integration tests in delivers-response-has-heartbeat-ok-but-includes.test.ts (4 tests) also pass.

Related

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31bffb4350

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +108 to 111
return payloads.some((payload) => {
const result = stripHeartbeatToken(payload.text, {
mode: "heartbeat",
maxAckChars: ackMaxChars,

Choose a reason for hiding this comment

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

P1 Badge Ignore empty payloads in heartbeat-only detection

Using payloads.some(... result.shouldSkip) makes any blank/undefined payload suppress delivery, because stripHeartbeatToken returns shouldSkip: true when raw is empty (src/auto-reply/heartbeat.ts:114-120). In the cron path this value directly gates announce delivery (src/cron/isolated-agent/run.ts:650), so a normal run like [{text:"Important update"},{text:" "}] is now treated as heartbeat-only and never sent. The previous .every() logic did not drop deliveries in this mixed-content case, so this change introduces false suppression whenever a whitespace/tool-empty payload is present.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

Fixed delivery suppression for cron agents that emit multiple text payloads before HEARTBEAT_OK. Changed isHeartbeatOnlyResponse() from .every() to .some() to correctly interpret the semantic meaning of the heartbeat token.

Key Changes:

  • If ANY payload contains HEARTBEAT_OK → suppress delivery (agent signals nothing needs attention)
  • If ANY payload has media → always deliver (preserves existing behavior)
  • Added 7 comprehensive unit tests covering the multi-payload scenario

Minor Style Issues:

  • JSDoc comment for isHeartbeatOnlyResponse still says "all payloads" but logic now checks "any payload"
  • Inline comment says "preceding text payloads" which implies ordering, but code doesn't enforce HEARTBEAT_OK position (this is likely fine given semantic meaning)

Confidence Score: 4/5

  • Safe to merge - correct logic fix with comprehensive test coverage and only minor documentation improvements suggested
  • Score reflects solid implementation that correctly fixes the reported bug with thorough test coverage. Only minor documentation/comment style issues noted. The logic change from .every() to .some() correctly interprets HEARTBEAT_OK semantics: if an agent includes the token anywhere in a response, it's signaling nothing needs user attention.
  • No files require special attention - both the implementation and tests are straightforward and correct

Last reviewed commit: 31bffb4

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 +104 to +107
// An agent may emit multiple text payloads (narration, tool summaries)
// before a final HEARTBEAT_OK. If *any* payload is a heartbeat ack token,
// the agent is signaling "nothing needs attention" — the preceding text
// payloads are just internal narration and should not be delivered.
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment says "preceding text payloads" but code doesn't enforce ordering

The comment implies HEARTBEAT_OK comes last, but the code would also suppress if HEARTBEAT_OK appears first or in the middle. Consider clarifying:

Suggested change
// An agent may emit multiple text payloads (narration, tool summaries)
// before a final HEARTBEAT_OK. If *any* payload is a heartbeat ack token,
// the agent is signaling "nothing needs attention" — the preceding text
// payloads are just internal narration and should not be delivered.
// An agent may emit multiple text payloads (narration, tool summaries)
// alongside a HEARTBEAT_OK token. If *any* payload is a heartbeat ack token,
// the agent is signaling "nothing needs attention" — other text payloads
// are just internal narration and should not be delivered.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/helpers.ts
Line: 104-107

Comment:
Comment says "preceding text payloads" but code doesn't enforce ordering

The comment implies HEARTBEAT_OK comes last, but the code would also suppress if HEARTBEAT_OK appears first or in the middle. Consider clarifying:

```suggestion
  // An agent may emit multiple text payloads (narration, tool summaries)
  // alongside a HEARTBEAT_OK token. If *any* payload is a heartbeat ack token,
  // the agent is signaling "nothing needs attention" — other text payloads
  // are just internal narration and should not be delivered.
```

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Additional Comments (1)

src/cron/isolated-agent/helpers.ts
JSDoc outdated - no longer checks if ALL payloads are heartbeat acks

/**
 * Check if the response should be suppressed because the agent signaled nothing needs attention.
 * Returns true if any payload contains a heartbeat ack token (HEARTBEAT_OK) and no media is present.
 */
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/helpers.ts
Line: 89-92

Comment:
JSDoc outdated - no longer checks if ALL payloads are heartbeat acks

```suggestion
/**
 * Check if the response should be suppressed because the agent signaled nothing needs attention.
 * Returns true if any payload contains a heartbeat ack token (HEARTBEAT_OK) and no media is present.
 */
```

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

@steipete steipete force-pushed the fix/cron-heartbeat-ok-multi-payload-delivery branch from 31bffb4 to 6a031e5 Compare March 2, 2026 22:16
steipete added a commit to adhishthite/openclaw that referenced this pull request Mar 2, 2026
Adhish and others added 2 commits March 2, 2026 22:16
…RTBEAT_OK

When a cron agent emits multiple text payloads (narration + tool
summaries) followed by a final HEARTBEAT_OK, the delivery suppression
check `isHeartbeatOnlyResponse` fails because it uses `.every()` —
requiring ALL payloads to be heartbeat tokens. In practice, agents
narrate their work before signaling nothing needs attention.

Fix: check if ANY payload contains HEARTBEAT_OK (`.some()`) while
preserving the media delivery exception (if any payload has media,
always deliver). This matches the semantic intent: HEARTBEAT_OK is
the agent's explicit signal that nothing needs user attention.

Real-world example: heartbeat agent returns 3 payloads:
1. "It's 12:49 AM — quiet hours. Let me run the checks quickly."
2. "Emails: Just 2 calendar invites. Not urgent."
3. "HEARTBEAT_OK"

Previously: all 3 delivered to Telegram. Now: correctly suppressed.

Related: openclaw#32013 (fixed a different HEARTBEAT_OK leak path via system
events in timer.ts)
@steipete steipete force-pushed the fix/cron-heartbeat-ok-multi-payload-delivery branch from 6a031e5 to 352bc2d Compare March 2, 2026 22:16
@steipete steipete merged commit 7253e91 into openclaw:main Mar 2, 2026
8 of 9 checks passed
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Landed via temp rebase onto main.

  • Gate: pnpm -s vitest run src/cron/isolated-agent/helpers.test.ts src/cron/isolated-agent.delivers-response-has-heartbeat-ok-but-includes.test.ts
  • Land commit: 352bc2d
  • Merge commit: 7253e91

Thanks @adhishthite!

planfit-alan pushed a commit to planfit/openclaw that referenced this pull request Mar 3, 2026
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HEARTBEAT_OK not suppressed when cron agent returns multiple text payloads

2 participants