Skip to content

fix: prevent HEARTBEAT_OK discard from swallowing steered user responses#30449

Closed
Jo2234 wants to merge 1 commit into
openclaw:mainfrom
Jo2234:fix/heartbeat-steer-response-swallowed
Closed

fix: prevent HEARTBEAT_OK discard from swallowing steered user responses#30449
Jo2234 wants to merge 1 commit into
openclaw:mainfrom
Jo2234:fix/heartbeat-steer-response-swallowed

Conversation

@Jo2234

@Jo2234 Jo2234 commented Mar 1, 2026

Copy link
Copy Markdown

Summary

Fix a bug where user messages sent during an active heartbeat run get silently swallowed when queue.mode is set to steer.

Problem

When steer mode injects a user message into a heartbeat run, the agent produces a combined response like:

HEARTBEAT_OK. Sure! Here is the answer to your question. Let me know if you need more details.

The current stripHeartbeatToken() logic strips HEARTBEAT_OK and then checks if the remaining text is ≤ maxAckChars (default: 300). Since many user responses are under 300 characters, the entire response is treated as a brief heartbeat ack and suppressed — the user never receives their answer.

Before (broken)

User sends message during heartbeat → Agent responds with HEARTBEAT_OK + answer
→ stripHeartbeatToken strips token, remaining text ≤ 300 chars
→ shouldSkip = true → delivery suppressed → user gets nothing

After (fixed)

User sends message during heartbeat → Agent responds with HEARTBEAT_OK + answer
→ stripHeartbeatToken strips token, remaining text ≤ 300 chars
→ BUT remaining text has multi-sentence structure → recognized as substantive response
→ shouldSkip = false → delivery proceeds → user gets their answer

Root Cause

In stripHeartbeatToken() (src/auto-reply/heartbeat.ts), the maxAckChars check does not distinguish between:

  • A brief heartbeat ack: "Nothing to report" (single fragment, no sentence structure)
  • A real user response: "Sure! Here is the answer. Let me know if you need more." (multi-sentence, addresses user)

Fix

Added a heuristic in the maxAckChars check: after stripping HEARTBEAT_OK, if the remaining text contains multi-sentence structure (sentence-ending punctuation [.!?] followed by more content), it is recognized as a substantive response and NOT suppressed.

Backward Compatibility

Input Before After
HEARTBEAT_OK suppressed suppressed
HEARTBEAT_OK 🦞 suppressed suppressed
HEARTBEAT_OK Nothing to report suppressed suppressed
HEARTBEAT_OK Sure! Here is the answer. suppressed delivered
HEARTBEAT_OK The server is fine. Deploy at 3:42. suppressed delivered

Files Changed

File Description
src/auto-reply/heartbeat.ts Added multi-sentence heuristic in stripHeartbeatToken()
src/auto-reply/heartbeat.test.ts 3 new test cases for steered response scenarios

Test Cases Added

  1. Multi-sentence steered response → preserved (not suppressed)
  2. Single-fragment ack → still correctly suppressed
  3. Question/answer pattern → preserved

Fixes #30197

When queue mode is set to 'steer', a user message that arrives during an
active heartbeat run is injected into that run. The agent may respond
with 'HEARTBEAT_OK' followed by a real answer to the user's question.

Previously, stripHeartbeatToken() stripped the HEARTBEAT_OK token and
then checked if the remaining text was <= maxAckChars (default 300).
Since many user responses are under 300 characters, the entire response
was treated as a brief heartbeat ack and suppressed — the user never
received their answer.

This commit adds a heuristic check: after stripping HEARTBEAT_OK, if
the remaining text contains multi-sentence structure (sentence-ending
punctuation followed by more content), it is recognized as a
substantive response rather than a brief ack, and delivery proceeds
normally.

The heuristic preserves backward compatibility:
- Pure HEARTBEAT_OK → still suppressed
- HEARTBEAT_OK with emoji/short junk → still suppressed
- HEARTBEAT_OK with single-fragment ack → still suppressed
- HEARTBEAT_OK followed by multi-sentence response → NOW DELIVERED

Added 3 test cases covering:
- Multi-sentence steered response after HEARTBEAT_OK (preserved)
- Single-fragment ack after HEARTBEAT_OK (still suppressed)
- Question/answer pattern response (preserved)

Fixes openclaw#30197

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

Copy link
Copy Markdown

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: 9263b43c2f

ℹ️ 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 +175 to +176
const hasMultipleSentences = /[.!?]\s+\S/.test(rest);
const hasSubstantiveContent = rest.length > 0 && hasMultipleSentences;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude token-leading punctuation from substantive check

The new hasMultipleSentences heuristic treats punctuation left behind by start-token stripping as evidence of a multi-sentence answer, so heartbeat acks like HEARTBEAT_OK. Nothing to report are now delivered instead of suppressed. In stripTokenAtEdges(), stripping a leading token keeps the ./! that follows the token, and this line then matches /[.!?]\s+\S/, setting shouldSkip to false for short non-actionable heartbeat text. This will cause regular heartbeat chatter to leak to users whenever the model formats acknowledgements as HEARTBEAT_OK. plus a short sentence.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes bug where user messages sent during active heartbeat runs get silently swallowed in steer mode. Added multi-sentence detection heuristic to distinguish between brief heartbeat acknowledgments and substantive combined responses.

Changes:

  • Added regex-based check (/[.!?]\s+\S/) to detect multi-sentence structure in stripHeartbeatToken()
  • Responses with multi-sentence patterns are now preserved instead of being suppressed
  • Added 3 new test cases covering multi-sentence responses, single-fragment acks, and question/answer patterns

Behavior:

  • Brief acks like "Nothing to report" remain suppressed ✓
  • Multi-sentence responses like "Sure! Here is the answer. Let me know if you need more." are now delivered ✓
  • Maintains backward compatibility for existing heartbeat behavior

Confidence Score: 4/5

  • Safe to merge with minimal risk - addresses the reported issue effectively
  • Well-tested fix that solves the main problem (multi-sentence responses being swallowed). The heuristic could be more comprehensive to handle single-sentence responses, but the current implementation is solid for the described use case. Minor comment/code discrepancy noted but doesn't affect functionality.
  • No files require special attention

Last reviewed commit: 9263b43

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +174 to +176
// patterns, treat it as a real response and do NOT skip.
const hasMultipleSentences = /[.!?]\s+\S/.test(rest);
const hasSubstantiveContent = rest.length > 0 && hasMultipleSentences;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

comment mentions "or starts with common response patterns" but only multi-sentence detection is implemented - single-sentence responses like "The answer is 42." would still be suppressed

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/heartbeat.ts
Line: 174-176

Comment:
comment mentions "or starts with common response patterns" but only multi-sentence detection is implemented - single-sentence responses like "The answer is 42." would still be suppressed

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

kevinWangSheng pushed a commit to kevinWangSheng/openclaw that referenced this pull request Mar 1, 2026
…user responses

When 'steer' mode injects a user message into a heartbeat run, the agent
produces a combined response like 'HEARTBEAT_OK Sure! Here is the answer.'
The previous logic stripped HEARTBEAT_OK and then checked if the remaining
text was ≤ maxAckChars (300). Since many user responses are under 300 chars,
the entire response was treated as a brief heartbeat ack and suppressed.

This fix adds a heuristic: after stripping HEARTBEAT_OK, if the remaining
text contains multi-sentence structure (sentence-ending punctuation [.!?]
followed by more content), it is recognized as a substantive response and
NOT suppressed.

Fixes openclaw#30449
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 5, 2026
@openclaw-barnacle

Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Steered user message swallowed when heartbeat run produces HEARTBEAT_OK

1 participant