Bug type
Behavior bug: wrong user-visible output, no crash.
Beta release blocker
No.
Summary
A heartbeat run triggered by a relayable async exec completion can deliver the raw protocol token HEARTBEAT_OK to the user. HEARTBEAT_OK is a heartbeat no-op/ack token and should be swallowed regardless of whether the heartbeat was triggered by a timer, cron event, or exec completion.
Observed on the shiki-tokyo host on 2026-05-06 (hostname, paths, config, and transcript contents redacted). Reproduced against current main source at 7f71e84248.
Steps to reproduce
- Enable heartbeat delivery to a user-visible target, for example
target: "last" with directPolicy: "allow".
- Queue a relayable exec completion system event, for example
Exec completed (review-run, code 0) :: review-worker spawn finished.
- Run heartbeat processing for the exec event and have the model/provider return exactly
HEARTBEAT_OK.
- Observe that outbound delivery receives and sends
text: "HEARTBEAT_OK".
Expected behavior
If the final normalized heartbeat response is pure HEARTBEAT_OK, gateway should treat it as a protocol no-op and skip user delivery. Exec completion reminders should still be delivered when the model returns a real summary or warning.
Actual behavior
normalizeHeartbeatReply correctly strips a pure HEARTBEAT_OK reply to shouldSkip=true, but the relayable exec completion branch restores the original reply text and clears the skip flag. That turns the protocol token into ordinary alert text.
Relevant current-source path:
src/agents/bash-tools.exec-runtime.ts:318 queues the exec completion event and requests heartbeat for exec-event.
src/infra/heartbeat-runner.ts:1040 collects exec completion events.
src/infra/heartbeat-runner.ts:1046 computes hasRelayableExecCompletion.
src/infra/heartbeat-events-filter.ts:36 treats structured exec completions with output, or failed completions, as relayable.
src/auto-reply/heartbeat.ts:147 strips heartbeat ack tokens.
src/infra/heartbeat-runner.ts:1689 intentionally prevents skip for exec completion events.
src/infra/heartbeat-runner.ts:1693 restores the original reply text when the normalized text is empty.
src/infra/heartbeat-runner.ts:1704 excludes relayable exec completions from shouldSkipMain.
src/infra/heartbeat-runner.ts:1861 sends normalized.text as an outbound payload.
OpenClaw version
Current-source repro: upstream/main at 7f71e84248.
Observed shiki-tokyo installed version: NOT_ENOUGH_INFO. The fresh logs show npm-global runtime paths and Node v22.22.0, but openclaw was not on the noninteractive SSH PATH for an exact --version check.
Operating system
Source-level repro: macOS development checkout.
Observed host: shiki-tokyo, Ubuntu Linux kernel 5.15.0-164-generic, x86_64.
Install method
Local repro: source checkout with pnpm.
Observed host: appears to be npm-global install from runtime log paths. Exact launch/install method: NOT_ENOUGH_INFO.
Model
Observed host logs: openai-codex/gpt-5.5.
Local deterministic repro: mocked heartbeat model boundary returning exactly HEARTBEAT_OK.
Provider / routing chain
Observed host: OpenClaw gateway -> openai-codex -> gpt-5.5, channel delivery through Feishu.
Local deterministic repro: runHeartbeatOnce with a lightweight test messaging plugin and mocked model response.
Additional provider/model setup details
Observed host heartbeat config, redacted to non-secret fields: target=last, directPolicy=allow, ackMaxChars=300, lightContext=true, skipWhenBusy=true, active hours in Asia/Shanghai.
Logs, screenshots, and evidence
Issue template/rules checked before filing: .github/ISSUE_TEMPLATE/bug_report.yml, .github/ISSUE_TEMPLATE/config.yml, and CONTRIBUTING.md.
Duplicate search was performed for HEARTBEAT_OK exec completion, notifyOnExit heartbeat, and HEARTBEAT_OK. Related issues are listed below, but this issue has a narrower deterministic current-source reproducer.
Local deterministic repro output, redacted:
event = Exec completed (review-run, code 0) :: review-worker spawn finished
model reply = HEARTBEAT_OK
result = {"status":"ran"}
outbound send = {"text":"HEARTBEAT_OK","to":"<redacted-target>"}
Targeted existing tests around adjacent behavior pass but do not cover this regression:
pnpm test src/infra/heartbeat-runner.ghost-reminder.test.ts -t "metadata-only successful exec completions|includes untrusted exec completion details"
=> 1 file passed, 2 tests passed, 18 skipped
Fresh shiki-tokyo log excerpts, redacted:
2026-05-06T09:13:29+08 Stripped stray HEARTBEAT_OK token <redacted> reply
2026-05-06T09:21:52+08 Stripped stray HEARTBEAT_OK token <redacted> reply
2026-05-06T09:21:57+08 Feishu final delivery preview referenced that HEARTBEAT_OK had been shown to the user and should have been swallowed.
2026-05-06T09:30:09+08 Stripped stray HEARTBEAT_OK token <redacted> reply
A plain heartbeat poll control case in the same log family showed assistantTexts:["HEARTBEAT_OK"] with didSendViaMessagingTool:false, which is the expected silent path. The failing path is the relayable exec completion branch above.
Impact and severity
Affected users: anyone with tools.exec.notifyOnExit enabled and heartbeat delivery allowed to a user-visible target.
Severity: medium-low but user-visible. It leaks an internal protocol token, creates confusing background notifications, and undermines the intended no-op semantics for heartbeat. The feature should still keep useful background exec completion notifications; disabling tools.exec.notifyOnExit is only a workaround.
Frequency: deterministic when a relayable exec completion is inspected and the model decides no user notification is needed by returning pure HEARTBEAT_OK.
Suggested fix
Do not restore or deliver original reply text when the normalized heartbeat response is a skip token. In particular, a pure normalized HEARTBEAT_OK should remain shouldSkip=true even when hasRelayableExecCompletion is true.
A regression test should cover: structured relayable exec completion with output + model reply HEARTBEAT_OK -> no outbound send, while the inspected exec event is still consumed/recorded according to the existing event lifecycle.
Related issues
Bug type
Behavior bug: wrong user-visible output, no crash.
Beta release blocker
No.
Summary
A heartbeat run triggered by a relayable async exec completion can deliver the raw protocol token
HEARTBEAT_OKto the user.HEARTBEAT_OKis a heartbeat no-op/ack token and should be swallowed regardless of whether the heartbeat was triggered by a timer, cron event, or exec completion.Observed on the
shiki-tokyohost on 2026-05-06 (hostname, paths, config, and transcript contents redacted). Reproduced against currentmainsource at7f71e84248.Steps to reproduce
target: "last"withdirectPolicy: "allow".Exec completed (review-run, code 0) :: review-worker spawn finished.HEARTBEAT_OK.text: "HEARTBEAT_OK".Expected behavior
If the final normalized heartbeat response is pure
HEARTBEAT_OK, gateway should treat it as a protocol no-op and skip user delivery. Exec completion reminders should still be delivered when the model returns a real summary or warning.Actual behavior
normalizeHeartbeatReplycorrectly strips a pureHEARTBEAT_OKreply toshouldSkip=true, but the relayable exec completion branch restores the original reply text and clears the skip flag. That turns the protocol token into ordinary alert text.Relevant current-source path:
src/agents/bash-tools.exec-runtime.ts:318queues the exec completion event and requests heartbeat forexec-event.src/infra/heartbeat-runner.ts:1040collects exec completion events.src/infra/heartbeat-runner.ts:1046computeshasRelayableExecCompletion.src/infra/heartbeat-events-filter.ts:36treats structured exec completions with output, or failed completions, as relayable.src/auto-reply/heartbeat.ts:147strips heartbeat ack tokens.src/infra/heartbeat-runner.ts:1689intentionally prevents skip for exec completion events.src/infra/heartbeat-runner.ts:1693restores the original reply text when the normalized text is empty.src/infra/heartbeat-runner.ts:1704excludes relayable exec completions fromshouldSkipMain.src/infra/heartbeat-runner.ts:1861sendsnormalized.textas an outbound payload.OpenClaw version
Current-source repro:
upstream/mainat7f71e84248.Observed
shiki-tokyoinstalled version: NOT_ENOUGH_INFO. The fresh logs show npm-global runtime paths and Nodev22.22.0, butopenclawwas not on the noninteractive SSH PATH for an exact--versioncheck.Operating system
Source-level repro: macOS development checkout.
Observed host:
shiki-tokyo, Ubuntu Linux kernel5.15.0-164-generic, x86_64.Install method
Local repro: source checkout with
pnpm.Observed host: appears to be npm-global install from runtime log paths. Exact launch/install method: NOT_ENOUGH_INFO.
Model
Observed host logs:
openai-codex/gpt-5.5.Local deterministic repro: mocked heartbeat model boundary returning exactly
HEARTBEAT_OK.Provider / routing chain
Observed host: OpenClaw gateway ->
openai-codex->gpt-5.5, channel delivery through Feishu.Local deterministic repro:
runHeartbeatOncewith a lightweight test messaging plugin and mocked model response.Additional provider/model setup details
Observed host heartbeat config, redacted to non-secret fields:
target=last,directPolicy=allow,ackMaxChars=300,lightContext=true,skipWhenBusy=true, active hours inAsia/Shanghai.Logs, screenshots, and evidence
Issue template/rules checked before filing:
.github/ISSUE_TEMPLATE/bug_report.yml,.github/ISSUE_TEMPLATE/config.yml, andCONTRIBUTING.md.Duplicate search was performed for
HEARTBEAT_OK exec completion,notifyOnExit heartbeat, andHEARTBEAT_OK. Related issues are listed below, but this issue has a narrower deterministic current-source reproducer.Local deterministic repro output, redacted:
Targeted existing tests around adjacent behavior pass but do not cover this regression:
Fresh
shiki-tokyolog excerpts, redacted:A plain heartbeat poll control case in the same log family showed
assistantTexts:["HEARTBEAT_OK"]withdidSendViaMessagingTool:false, which is the expected silent path. The failing path is the relayable exec completion branch above.Impact and severity
Affected users: anyone with
tools.exec.notifyOnExitenabled and heartbeat delivery allowed to a user-visible target.Severity: medium-low but user-visible. It leaks an internal protocol token, creates confusing background notifications, and undermines the intended no-op semantics for heartbeat. The feature should still keep useful background exec completion notifications; disabling
tools.exec.notifyOnExitis only a workaround.Frequency: deterministic when a relayable exec completion is inspected and the model decides no user notification is needed by returning pure
HEARTBEAT_OK.Suggested fix
Do not restore or deliver original reply text when the normalized heartbeat response is a skip token. In particular, a pure normalized
HEARTBEAT_OKshould remainshouldSkip=trueeven whenhasRelayableExecCompletionis true.A regression test should cover: structured relayable exec completion with output + model reply
HEARTBEAT_OK-> no outbound send, while the inspected exec event is still consumed/recorded according to the existing event lifecycle.Related issues
HEARTBEAT_OK.