Skip to content

fix(cron): suppress HEARTBEAT_OK summary from leaking into main session#32093

Merged
steipete merged 2 commits intoopenclaw:mainfrom
scoootscooob:fix/cron-heartbeat-ok-leak
Mar 2, 2026
Merged

fix(cron): suppress HEARTBEAT_OK summary from leaking into main session#32093
steipete merged 2 commits intoopenclaw:mainfrom
scoootscooob:fix/cron-heartbeat-ok-leak

Conversation

@scoootscooob
Copy link
Contributor

Closes #32013

Summary

  • When an isolated cron agent returns HEARTBEAT_OK (nothing to announce), direct delivery is correctly skipped via skipHeartbeatDelivery
  • Bug: the fallback path in timer.ts still enqueues the summary as a system event to the main session because delivered=false and deliveryAttempted=false
  • Fix: gate the fallback enqueue on isCronSystemEvent(summaryText) which filters out heartbeat ack tokens and exec-completion noise
  • Uses the existing isCronSystemEvent utility from heartbeat-events-filter.ts — no new abstractions

Changes

  • src/cron/service/timer.ts: import isCronSystemEvent, add filter check before enqueueSystemEvent call
  • src/cron/service.heartbeat-ok-summary-suppressed.test.ts: 2 tests — HEARTBEAT_OK suppressed, real summaries still forwarded

What did NOT change

  • No changes to isolated agent run logic, delivery dispatch, or heartbeat detection
  • No changes to how real cron summaries are forwarded — only heartbeat-only ack tokens are filtered

Test plan

  • HEARTBEAT_OK summary does not enqueue a system event
  • Real cron summaries ("Weather update: sunny, 72°F") still enqueue correctly
  • npx tsgo type check passes

🤖 Generated with Claude Code

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: 46d43cc1b8

ℹ️ 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".

res.status === "error" && res.errorKind === "delivery-target" && deliveryPlan.requested;
if (
summaryText &&
isCronSystemEvent(summaryText) &&

Choose a reason for hiding this comment

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

P2 Badge Narrow heartbeat suppression to token-only summaries

isCronSystemEvent(summaryText) is a broader classifier than the bug requires: it returns false for any text containing "heartbeat poll", "heartbeat wake", "exec finished", or starting with HEARTBEAT_OK plus punctuation (see isCronSystemEvent in src/infra/heartbeat-events-filter.ts). In this fallback path (deliveryPlan.requested && !res.delivered && res.deliveryAttempted !== true), that means legitimate isolated-job summaries can now be dropped instead of enqueued to the main session, e.g. a real summary like "Heartbeat wake checklist completed" when direct delivery is skipped. The gate here should only suppress heartbeat-ack-only outputs, not all strings matching the broader system-event filter.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR fixes a bug where HEARTBEAT_OK acknowledgment tokens were leaking into the main session as system events. When an isolated cron agent returns HEARTBEAT_OK (indicating nothing to announce), the direct delivery was correctly skipped, but the fallback path in timer.ts was still enqueuing the summary to the main session because delivered=false and deliveryAttempted=false.

The fix adds a filter check using the existing isCronSystemEvent() utility function (from heartbeat-events-filter.ts) before enqueuing system events. This function returns false for internal heartbeat tokens like HEARTBEAT_OK, "heartbeat poll", "heartbeat wake", and "exec finished" events, effectively filtering them out while allowing real cron summaries to pass through.

Key changes:

  • Added isCronSystemEvent(summaryText) condition in the fallback enqueue path at src/cron/service/timer.ts:998
  • Added comprehensive test coverage for both suppression and passthrough scenarios
  • No changes to delivery logic, isolated agent runs, or heartbeat detection - only filters the fallback path

The implementation is consistent with how isCronSystemEvent is used elsewhere in the codebase (e.g., heartbeat-runner.ts line 577).

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • This is a well-targeted bug fix with clear logic and comprehensive test coverage. The change adds a single filter condition using an existing, well-tested utility function (isCronSystemEvent). The implementation is consistent with how this function is used elsewhere in the codebase. Tests verify both that unwanted tokens are suppressed and that real summaries still pass through correctly. No breaking changes or risky modifications.
  • No files require special attention

Last reviewed commit: 46d43cc

scoootscooob and others added 2 commits March 2, 2026 19:50
…on (openclaw#32013)

When an isolated cron agent returns HEARTBEAT_OK (nothing to announce),
the direct delivery is correctly skipped, but the fallback path in
timer.ts still enqueues the summary as a system event to the main
session. Filter out heartbeat-only summaries using isCronSystemEvent
before enqueuing, so internal ack tokens never reach user conversations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@steipete steipete force-pushed the fix/cron-heartbeat-ok-leak branch from 46d43cc to 9a74498 Compare March 2, 2026 19:51
@steipete steipete merged commit 160dad5 into openclaw:main Mar 2, 2026
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Landed via temp rebase onto main.

  • Gate: pnpm -s vitest run src/cron/service.heartbeat-ok-summary-suppressed.test.ts
  • Land commit: 9a74498
  • Merge commit: ${merge_sha}

Thanks @scoootscooob!

@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Correction: merge commit SHA

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: 9a74498567

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

res.status === "error" && res.errorKind === "delivery-target" && deliveryPlan.requested;
if (
summaryText &&
isCronSystemEvent(summaryText) &&

Choose a reason for hiding this comment

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

P2 Badge Use heartbeat-only classifier for fallback suppression

The new fallback gate uses isCronSystemEvent(summaryText), but isolated-delivery skipping uses isHeartbeatOnlyResponse(...) (src/cron/isolated-agent/helpers.ts:93), and these classifiers are not equivalent: heartbeat-only outputs like <b>HEARTBEAT_OK</b> are treated as heartbeat-only (delivery skipped) while isCronSystemEvent still returns true, so this branch will enqueue that ack text to the main session. In other words, the leak still occurs for formatted HEARTBEAT_OK variants; the fallback suppression should use the same heartbeat-token stripping logic as the delivery-skip path.

Useful? React with 👍 / 👎.

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 leaks into user-visible replies when cron announcements queue during active conversation

2 participants