openclaw - 💡(How to fix) Fix REPLY_SKIP / control-reply suppression not wired into cron-announce delivery path

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…

The control-reply suppression layer (isSuppressedControlReplyText in dist/control-reply-text-*.js) correctly recognizes REPLY_SKIP, ANNOUNCE_SKIP, and the silent-reply token, and is wired into the chat-display and live-chat-projector paths. It is not wired into the cron-announce delivery path. As a result, any agentTurn cron whose prompt documents the REPLY_SKIP convention (the documented way to say "I have nothing to report — suppress this post") will post the literal string REPLY_SKIP to the configured Discord channel every time the agent returns it.

Root Cause

Root Cause (in OpenClaw source)

Fix Action

Fix / Workaround

Wire isSuppressedControlReplyText into the cron-announce final-delivery code path. Likely insertion point: the function that builds the Discord (or other channel) POST body from the assistant reply text — should early-return / no-op if isSuppressedControlReplyText(reply.trim()) === true. Same semantics already used on the chat-display side; should be a one-import + one-conditional patch.

Bonus: also recognize markdown-emphasis-wrapped variants (**REPLY_SKIP**, `REPLY_SKIP`) since some models add emphasis when emitting bare tokens. (Our userspace workaround at ~/.openclaw/scripts/lib/agent-cron-runner.sh matches both bare and wrapped forms.)

Our Workaround

RAW_BUFFERClick to expand / collapse

OpenClaw version observed: First observed 2026.4.29; re-verified still present in 2026.5.18 (installed at ~/.npm-global/lib/node_modules/openclaw). On 2026.5.18 only the chat-side files (chat-DE6J9HvH.js, chat-display-projection-BCDIrRxt.js, control-reply-text-Bzp3N0lQ.js, live-chat-projector-BtBl94Tc.js) import isSuppressedControlReplyText; no delivery-*.js / *cron*.js / best-effort-delivery-*.js does. Filed by: SKP Team (Shawn Powers, skpteam.casa) Date observed: First leak 2026-05-11; confirmed sustained 2026-05-14 Severity (from user perspective): Medium — leaks internal control tokens to end-user channels; defeats the documented contract for silent cron runs.


Summary

The control-reply suppression layer (isSuppressedControlReplyText in dist/control-reply-text-*.js) correctly recognizes REPLY_SKIP, ANNOUNCE_SKIP, and the silent-reply token, and is wired into the chat-display and live-chat-projector paths. It is not wired into the cron-announce delivery path. As a result, any agentTurn cron whose prompt documents the REPLY_SKIP convention (the documented way to say "I have nothing to report — suppress this post") will post the literal string REPLY_SKIP to the configured Discord channel every time the agent returns it.

Reproduction

  1. Configure an agentTurn cron with delivery.mode: "announce" and a Discord channel.
  2. Send a prompt that includes the standard contract language:

    "Return the literal string REPLY_SKIP if there is nothing to report."

  3. Trigger the cron when the precondition for REPLY_SKIP is true.
  4. Expected: Gateway suppresses the post; no Discord message.
  5. Actual: The exact string REPLY_SKIP is POSTed to the Discord channel.

Confirmed across two enabled crons on this host (pace:it-triage at 9 AM + 2 PM CT, pace:help-page-review at 10:15 AM CT) and one already-retired cron (pace:marketing-oneup-review). 9+ visible leaks across the 4-day window 2026-05-11 → 2026-05-14.

Markdown-wrapped variants also leak when the model emits them — observed **REPLY_SKIP**\n\nThe collector ran... posted verbatim.

Root Cause (in OpenClaw source)

Grep across ~/.npm-global/lib/node_modules/openclaw/dist/:

  • isSuppressedControlReplyText defined in control-reply-text-*.js
  • Imported and called by chat-*.js, chat-display-projection-*.js, live-chat-projector-*.js
  • NOT imported by any of: server-cron-*.js, cron-store-runtime-*.js, delivery-*.js, best-effort-delivery-*.js, run-delivery.runtime-*.js, delivery-queue-*.js, delivery-queue-runtime-*.js, delivery-outbound.runtime-*.js, source-reply-delivery-mode-*.js, delivery-target.runtime-*.js, or the other 14 files in the delivery group.

In other words, when a cron's final assistant text is sent via delivery.mode: "announce", the suppression function isn't consulted before the Discord POST is built. The agent reply is posted verbatim regardless of content.

Proposed Fix

Wire isSuppressedControlReplyText into the cron-announce final-delivery code path. Likely insertion point: the function that builds the Discord (or other channel) POST body from the assistant reply text — should early-return / no-op if isSuppressedControlReplyText(reply.trim()) === true. Same semantics already used on the chat-display side; should be a one-import + one-conditional patch.

Bonus: also recognize markdown-emphasis-wrapped variants (**REPLY_SKIP**, `REPLY_SKIP`) since some models add emphasis when emitting bare tokens. (Our userspace workaround at ~/.openclaw/scripts/lib/agent-cron-runner.sh matches both bare and wrapped forms.)

Our Workaround

Deterministic LaunchAgent + agent-cron-runner.sh wrapper that invokes the agent via /v1/chat/completions, applies the suppression contract in userspace, and only POSTs non-suppressed replies. Affected gateway crons set enabled: false. Full architecture in ~/.openclaw/knowledge-base/lessons-learned/2026-05-14-reply-skip-leak-and-agent-cron-runner.md.

We'd happily switch back to the gateway-managed agent-turn delivery once isSuppressedControlReplyText lands in the cron-announce path — the userspace wrapper is functional but reimplements logic that the gateway already mostly has.

Reproducibility on a Fresh Install

Should reproduce on any OpenClaw 2026.4.x install with:

  • A configured agent (any model)
  • A cron job using payload.kind: "agentTurn", delivery.mode: "announce", Discord channel destination
  • An agent that returns the literal string REPLY_SKIP

The contract is documented in agent skill SKILL.md files generated/recommended by OpenClaw tooling, so this isn't a fringe use case — it's the recommended pattern for silent cron runs.


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…

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 - 💡(How to fix) Fix REPLY_SKIP / control-reply suppression not wired into cron-announce delivery path