fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK#32131
Conversation
There was a problem hiding this comment.
💡 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".
| return payloads.some((payload) => { | ||
| const result = stripHeartbeatToken(payload.text, { | ||
| mode: "heartbeat", | ||
| maxAckChars: ackMaxChars, |
There was a problem hiding this comment.
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 SummaryFixed delivery suppression for cron agents that emit multiple text payloads before Key Changes:
Minor Style Issues:
Confidence Score: 4/5
Last reviewed commit: 31bffb4 |
| // 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. |
There was a problem hiding this 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:
| // 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.
Additional Comments (1)
Prompt To Fix With AIThis 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. |
31bffb4 to
6a031e5
Compare
…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)
6a031e5 to
352bc2d
Compare
|
Landed via temp rebase onto main.
Thanks @adhishthite! |
) (thanks @adhishthite) (cherry picked from commit 7253e91)
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 withHEARTBEAT_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:
Fix
Change
isHeartbeatOnlyResponse()from.every()to.some():The media check is lifted to a separate
.some()pass for clarity.Tests
Added 7 unit tests for
isHeartbeatOnlyResponseinhelpers.test.ts:All 20 tests in
helpers.test.tspass. Integration tests indelivers-response-has-heartbeat-ok-but-includes.test.ts(4 tests) also pass.Related