NO_REPLY sentinel leaks in cron direct delivery fixed
Cron jobs that use the announce delivery mode will no longer spam Telegram and other external channels with internal NO_REPLY system messages.
The NO_REPLY sentinel was slipping through to external channels during cron job executions. When a cron job's isolated session uses delivery: "announce", the direct delivery path failed to filter out the internal sentinel token before sending messages to Telegram or other external channels.
The issue affected a specific code path in the cron delivery system. The deliverViaDirect function constructs outbound payloads from synthesized text but never checked for the NO_REPLY sentinel before delivering. The parallel finalizeTextDelivery path already had this check in place.
Additionally, when silent replies were filtered out, the code returned early without updating delivery state flags. This caused a secondary bug: the heartbeat timer would fire a fallback delivery, resulting in duplicate announcements.
The fix adds the sentinel filter to the direct delivery path and ensures delivery state is properly marked so the job resolves as a silent success rather than triggering a retry. Silent cron replies are now handled consistently regardless of which internal delivery path is used.
View Original GitHub Description
Summary
- Problem: When a cron job's isolated session uses
delivery: "announce", thedeliverViaDirectfunction insrc/cron/isolated-agent/delivery-dispatch.tsfails to filter out theNO_REPLYsentinel token. This causes the internal sentinel to leak and be sent as an actual message to external channels (like Telegram). Furthermore, simply returning early without updating delivery state flags causes the heartbeat timer to fire a fallback delivery, resulting in a double-announce bug where the sentinel is still sent. - Why it matters: It degrades the user experience by spamming their chat channels with internal
NO_REPLYsystem messages during automated cron executions.
Root Cause
The deliverViaDirect path constructs outbound payloads from synthesizedText or deliveryPayloads but never checks for the NO_REPLY sentinel before calling deliverOutboundPayloads. The parallel finalizeTextDelivery path already has this check and correctly returns delivered: true on NO_REPLY, but deliverViaDirect (used for structured/threaded deliveries) was missing it entirely.
What Changed
| File | Change | Why |
|---|---|---|
src/cron/isolated-agent/delivery-dispatch.ts | Import isSilentReplyText and filter rawPayloads before outbound delivery | Prevents NO_REPLY sentinel from leaking to external channels |
src/cron/isolated-agent/delivery-dispatch.ts | Set deliveryAttempted = true and delivered = true on filtered-empty early return, and return a proper status: "ok" result | Prevents shouldEnqueueCronMainSummary timer guard from firing a fallback enqueueSystemEvent; ensures resolveDeliveryStatus persists the job as a silent success — consistent with finalizeTextDelivery behavior |
src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts | Added 2 test cases (exact match + whitespace variant) with deliveryAttempted, delivered, and shouldEnqueueCronMainSummary assertions | Covers the regression path and timer-guard behavior, consistent with existing suppression tests in this file |
What Did NOT Change
The finalizeTextDelivery path remains untouched as it already has the correct SILENT_REPLY_TOKEN check. The frontend defense-in-depth logic also remains unchanged.
Change Type (select all)
- Bug fix
Scope (select all touched areas)
- Gateway / orchestration
Linked Issue/PR
Fixes #45705
AI-Assisted Contribution
- AI Usage: This PR was developed with AI assistance.
- Human Review: I have personally reviewed, designed, and fully understand all the code changes.
- Testing: Fully tested locally with my OpenClaw instance. All 15 tests in
delivery-dispatch.double-announce.test.tspass. - Prompt Context: The AI agent analyzed the issue, traced the execution path of cron deliveries, identified the missing sentinel check in
deliverViaDirect, and iteratively refined the fix based on automated reviews (greptile-apps, chatgpt-codex-connector) to properly handle delivery state flags (deliveryAttemptedanddelivered) to prevent timer fallbacks and ensure delivery status consistency.