openclaw - ✅(Solved) Fix [Bug]: HEARTBEAT_OK final heartbeat payload can leak to channels [2 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#72217Fetched 2026-04-27 05:33:09
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
cross-referenced ×3closed ×1commented ×1

HEARTBEAT_OK can be delivered as a visible channel message from a heartbeat run because final heartbeat reply payloads bypass the existing heartbeat-token stripping path.

Root Cause

HEARTBEAT_OK can be delivered as a visible channel message from a heartbeat run because final heartbeat reply payloads bypass the existing heartbeat-token stripping path.

Fix Action

Fixed

PR fix notes

PR #72218: fix(heartbeat): suppress final ok payloads

Description (problem / solution / changelog)

Summary

  • Problem: OK-only heartbeat final replies can leak into channel sessions because heartbeat payloads bypass final reply token stripping.
  • Why it matters: routine internal heartbeat acknowledgments like HEARTBEAT_OK can appear as unsolicited Telegram/WhatsApp/chat messages.
  • What changed: final heartbeat payloads now run through the same HEARTBEAT_OK sanitation path as non-heartbeat final replies.
  • What did NOT change (scope boundary): heartbeat scheduling, alert delivery, async exec completion handling, and channel-specific routing were not changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #72217
  • Related #71860
  • Related #69492
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: buildReplyPayloads returned raw params.payloads whenever params.isHeartbeat was true, so heartbeat final payloads skipped the existing stripHeartbeatToken sanitation branch.
  • Missing detection / guardrail: there was no payload-layer regression asserting that an exact HEARTBEAT_OK heartbeat final reply is dropped before channel delivery.
  • Contributing context (if known): docs already describe default OK-ack suppression; this path did not match that contract.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/auto-reply/reply/agent-runner-payloads.test.ts
  • Scenario the test should lock in: isHeartbeat: true plus exact HEARTBEAT_OK produces no final reply payloads; real heartbeat alert text remains deliverable; media-bearing payloads keep media after token stripping.
  • Why this is the smallest reliable guardrail: the bug is the final payload sanitation branch in buildReplyPayloads, so the focused payload test exercises the failing conditional directly without channel-specific fixtures.
  • Existing test that already covers this (if any): none for isHeartbeat: true final payload sanitation.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

OK-only heartbeat acknowledgments should no longer appear as visible channel messages by default. Real heartbeat alert content should still be delivered.

Diagram (if applicable)

Before:
heartbeat final payload -> isHeartbeat bypass -> HEARTBEAT_OK can remain visible

After:
heartbeat final payload -> shared token sanitation -> OK-only payload dropped, alerts continue

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: local source checkout on current origin/main
  • Model/provider: not provider-specific
  • Integration/channel (if any): final reply payload path used before channel delivery
  • Relevant config (redacted): default heartbeat OK visibility behavior documented in docs/gateway/heartbeat.md

Steps

  1. Call buildReplyPayloads with isHeartbeat: true and payloads: [{ text: "HEARTBEAT_OK" }].
  2. Before this PR, observe the final reply payload remains deliverable.
  3. After this PR, observe no final reply payload is returned.

Expected

  • OK-only heartbeat final replies are suppressed before channel delivery.
  • Real alert text from heartbeat runs is preserved.

Actual

  • Before this PR, OK-only heartbeat final replies bypassed token stripping when isHeartbeat was true.
  • After this PR, they use the shared token sanitation path and are dropped when text-only.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Failing-before evidence from the new regression test:

FAIL src/auto-reply/reply/agent-runner-payloads.test.ts > buildReplyPayloads heartbeat token sanitation > suppresses OK-only final payloads for heartbeat runs
AssertionError: expected [ { text: 'HEARTBEAT_OK', ... } ] to have a length of +0 but got 1

Passing-after evidence:

node scripts/test-projects.mjs src/auto-reply/reply/agent-runner-payloads.test.ts
Test Files  1 passed (1)
Tests  27 passed (27)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: exact heartbeat OK final payload is suppressed; non-heartbeat OK suppression still works; heartbeat alert text is preserved; media-bearing heartbeat payload keeps media after OK text stripping.
  • Edge cases checked: media-bearing payload with OK-only text, existing media dedupe tests, existing silent-turn tests, existing block-streaming final payload tests.
  • What you did not verify: live Telegram/WhatsApp delivery against a real account in this PR branch.

Additional local checks:

pnpm check:changed
# passed: conflict marker check, core typecheck, core test typecheck, core lint, import-cycle check, auth/webhook guards, focused test

pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/agent-runner-payloads.ts src/auto-reply/reply/agent-runner-payloads.test.ts
# passed

git diff --check
# passed

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Real heartbeat alert content could be over-suppressed.
    • Mitigation: Added a regression test showing alert text without the OK token is preserved for heartbeat runs.
  • Risk: Media payloads could be dropped when OK text is stripped.
    • Mitigation: Added a regression test showing media remains renderable after OK text stripping.

Changed files

  • src/auto-reply/reply/agent-runner-payloads.test.ts (modified, +58/-0)
  • src/auto-reply/reply/agent-runner-payloads.ts (modified, +19/-21)

PR #72253: fix(heartbeat): keep benign exec completions internal

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: benign async exec completions, including restart-cleanup signal SIGTERM, can be relayed into user chat as noisy failure summaries.
  • Why it matters: routine gateway/session restarts can produce Telegram/WhatsApp messages that look like actionable command failures even when there is no user-facing problem.
  • What changed: successful structured exec completions and structured SIGTERM cleanup completions are classified as internal-only, suppress visible HEARTBEAT_OK, and use normal ack skipping.
  • What did NOT change (scope boundary): real non-SIGTERM failures such as Exec failed (..., code 1) :: build failed still relay to the user.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #72252
  • Related #69492
  • Related #67273
  • Related #72217
  • Related #72218
  • Related #71213
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: heartbeat treated every exec completion event as user-relayable, and exec completion handling intentionally bypassed normal HEARTBEAT_OK ack skipping so results would not be lost.
  • Missing detection / guardrail: there was no distinction between actionable exec failures and non-actionable structured completions/cleanup terminations.
  • Contributing context (if known): this is a narrower tactical fix for one noisy path while the broader per-event audience classification work remains tracked separately.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/infra/heartbeat-events-filter.test.ts, src/infra/heartbeat-runner.returns-default-unset.test.ts
  • Scenario the test should lock in: code 0 and signal SIGTERM structured exec completions stay internal and do not send chat output; code 1 failures still relay.
  • Why this is the smallest reliable guardrail: the bug is in heartbeat event classification and delivery suppression, so direct prompt and runner tests cover the decision seam without depending on full gateway e2e timing.
  • Existing test that already covers this (if any): existing exec completion tests covered relay paths but not benign/internal-only structured completions.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Benign async command completions and restart-cleanup SIGTERM events no longer produce visible Telegram/WhatsApp/chat messages. Real async command failures continue to notify users.

Diagram (if applicable)

Before:
[restart cleanup SIGTERM] -> [exec completion heartbeat] -> [visible chat failure summary]

After:
[restart cleanup SIGTERM] -> [internal heartbeat handling] -> [no visible chat noise]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node v24.15.0, pnpm
  • Model/provider: N/A
  • Integration/channel (if any): heartbeat async exec delivery to chat channels
  • Relevant config (redacted): heartbeat delivery enabled; async exec completion events queued

Steps

  1. Queue Exec completed (abc12345, code 0) :: backup complete for a heartbeat session.
  2. Queue Exec failed (abc12345, signal SIGTERM) :: openclaw gateway help text for a heartbeat session.
  3. Queue Exec failed (abc12345, code 1) :: build failed for a heartbeat session.

Expected

  • code 0 and signal SIGTERM structured completions are handled internally and do not send visible chat messages.
  • code 1 structured failures still relay to the configured chat target.

Actual

  • Before this change, all exec completions were treated as user-relay completions, so benign completions could surface as visible chat noise.
  • After this change, only actionable exec failures remain relayable.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • node scripts/test-projects.mjs src/infra/heartbeat-events-filter.test.ts src/infra/heartbeat-runner.returns-default-unset.test.ts passed.
    • pnpm exec oxfmt --check --threads=1 src/infra/heartbeat-events-filter.ts src/infra/heartbeat-events-filter.test.ts src/infra/heartbeat-runner.ts src/infra/heartbeat-runner.returns-default-unset.test.ts passed.
    • git diff --check passed.
    • pnpm build passed.
  • Edge cases checked:
    • Successful structured exec completion stays internal.
    • signal SIGTERM structured exec completion stays internal.
    • Non-SIGTERM code 1 structured failure still relays.
    • Mixed internal success plus real failure filters the success output and relays the real failure only.
  • What you did not verify:
    • pnpm check:changed did not complete cleanly locally because test/gateway.multi.e2e.test.ts repeatedly timed out waiting for an unrelated final chat event after the required dist/index.js build prerequisite was fixed. The focused heartbeat tests, formatter, typecheck/lint stages shown before the e2e lane, and build passed locally.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: a meaningful SIGTERM could be hidden from the user.
    • Mitigation: this only internalizes the structured Exec failed (..., signal SIGTERM) completion shape; non-SIGTERM failures such as code 1 continue to relay and are covered by tests.
  • Risk: mixed event batches could drop useful output.
    • Mitigation: internal-only completion output is filtered, while any real failure in the same batch remains relayable and covered by tests.

Changed files

  • src/agents/bash-tools.exec-runtime.ts (modified, +2/-0)
  • src/agents/bash-tools.test.ts (modified, +7/-1)
  • src/infra/heartbeat-events-filter.test.ts (modified, +54/-0)
  • src/infra/heartbeat-events-filter.ts (modified, +40/-9)
  • src/infra/heartbeat-runner.returns-default-unset.test.ts (modified, +231/-0)
  • src/infra/heartbeat-runner.ts (modified, +64/-22)
  • src/infra/system-events.ts (modified, +4/-0)

Code Example

Observed user-visible symptom, redacted:
Sammy: HEARTBEAT_OK
User: Why did you send this here

Relevant code path:
src/auto-reply/reply/agent-runner-payloads.ts

Current behavior:
const sanitizedPayloads = params.isHeartbeat
  ? params.payloads
  : params.payloads.flatMap(... stripHeartbeatToken ...)

Effect:
Heartbeat final payloads bypass the same `stripHeartbeatToken` branch that suppresses OK-only text for non-heartbeat final replies.
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

HEARTBEAT_OK can be delivered as a visible channel message from a heartbeat run because final heartbeat reply payloads bypass the existing heartbeat-token stripping path.

Steps to reproduce

  1. Run a proactive heartbeat where the model returns an OK-only final reply: HEARTBEAT_OK.
  2. Let the heartbeat final reply flow through buildReplyPayloads with isHeartbeat: true.
  3. Observe that the payload is not passed through stripHeartbeatToken because sanitizedPayloads uses the raw params.payloads for heartbeat runs.

Expected behavior

docs/gateway/heartbeat.md says HEARTBEAT_OK acknowledgments are suppressed by default while real alert content is delivered. An OK-only heartbeat should therefore stay internal and not create a user-visible Telegram/WhatsApp/chat message.

Actual behavior

The OK-only heartbeat payload can remain deliverable because src/auto-reply/reply/agent-runner-payloads.ts skips the normal token-stripping branch when params.isHeartbeat is true.

OpenClaw version

2026.4.24 installed runtime observed locally; root-cause code path also exists on current origin/main at time of filing.

Operating system

Ubuntu VPS / Linux

Install method

npm global for observed runtime; source checkout for root-cause inspection.

Model

Configured assistant model for the affected agent session. Exact provider is not needed for the code-path repro.

Provider / routing chain

OpenClaw heartbeat runner -> agent reply -> final reply payload construction -> channel delivery.

Additional provider/model setup details

No provider-specific behavior is required to reproduce the code-path issue. The failure is in final payload sanitation after the model has already returned HEARTBEAT_OK.

Logs, screenshots, and evidence

Observed user-visible symptom, redacted:
Sammy: HEARTBEAT_OK
User: Why did you send this here

Relevant code path:
src/auto-reply/reply/agent-runner-payloads.ts

Current behavior:
const sanitizedPayloads = params.isHeartbeat
  ? params.payloads
  : params.payloads.flatMap(... stripHeartbeatToken ...)

Effect:
Heartbeat final payloads bypass the same `stripHeartbeatToken` branch that suppresses OK-only text for non-heartbeat final replies.

Related context:

  • #71860 is adjacent async exec success suppression, not this final heartbeat payload bypass.
  • #69492 tracks a broader system-event audience classification proposal.
  • #66154, #17646, and #71381 describe related visible heartbeat/system-event symptoms.

Impact and severity

Affected: channel sessions that receive proactive heartbeat final replies, including direct-message integrations. Severity: annoying / confusing UX; not a crash or data-loss bug. Frequency: occurs when a heartbeat run produces an OK-only final reply and reaches the final payload delivery path. Consequence: routine internal heartbeat acknowledgments can appear as unsolicited user-visible messages.

Additional information

A narrow fix is to run heartbeat final payloads through the existing heartbeat-token stripping logic instead of bypassing final payload sanitation for isHeartbeat: true. Regression coverage should live in src/auto-reply/reply/agent-runner-payloads.test.ts and assert that isHeartbeat: true plus exact HEARTBEAT_OK produces no reply payloads while real alert text remains deliverable.

extent analysis

TL;DR

The issue can be fixed by modifying the sanitizedPayloads logic in agent-runner-payloads.ts to run heartbeat final payloads through the existing heartbeat-token stripping logic.

Guidance

  • Identify the sanitizedPayloads assignment in src/auto-reply/reply/agent-runner-payloads.ts and modify it to apply the stripHeartbeatToken logic to heartbeat final payloads.
  • Verify that the change does not introduce any regressions by adding test cases to src/auto-reply/reply/agent-runner-payloads.test.ts.
  • Ensure that the fix does not affect the delivery of real alert content by testing scenarios where the model returns non-OK final replies.
  • Review related issues (#71860, #69492, #66154, #17646, and #71381) to ensure that the fix does not introduce any inconsistencies with other system-event or heartbeat behaviors.

Example

const sanitizedPayloads = params.payloads.flatMap(payload => {
  if (payload === 'HEARTBEAT_OK' && params.isHeartbeat) {
    return []; // strip HEARTBEAT_OK payload for heartbeat runs
  }
  return stripHeartbeatToken(payload);
});

Notes

The proposed fix assumes that the stripHeartbeatToken function correctly handles heartbeat tokens and does not introduce any side effects. Additional testing and review may be necessary to ensure that the fix is robust and compatible with all supported scenarios.

Recommendation

Apply the workaround by modifying the sanitizedPayloads logic to run heartbeat final payloads through the existing heartbeat-token stripping logic, as this fix directly addresses the root cause of the issue and does not require any version upgrades.

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…

FAQ

Expected behavior

docs/gateway/heartbeat.md says HEARTBEAT_OK acknowledgments are suppressed by default while real alert content is delivered. An OK-only heartbeat should therefore stay internal and not create a user-visible Telegram/WhatsApp/chat message.

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 [Bug]: HEARTBEAT_OK final heartbeat payload can leak to channels [2 pull requests, 1 comments, 2 participants]