openclaw - ✅(Solved) Fix [Bug]: (cron): announce delivery ignores NO_REPLY when agent prepends other text [3 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#64976Fetched 2026-04-12 13:26:09
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Author
Timeline (top)
referenced ×3cross-referenced ×2labeled ×2commented ×1

Cron announce delivery sends the full agent response when it contains NO_REPLY mixed with other text; the NO_REPLY suppression only works when it is the entire response.

Root Cause

Cron announce delivery sends the full agent response when it contains NO_REPLY mixed with other text; the NO_REPLY suppression only works when it is the entire response.

Fix Action

Fix / Workaround

Affected: All users with announce-mode isolated crons using NO_REPLY suppression Severity: Annoying — causes false deliveries to user-facing channels Frequency: 14/15 observed cron deliveries over 8 days Consequence: Users receive noise messages that should have been suppressed; workaround requires adding fragile anti-reasoning prompt rules to every cron

PR fix notes

PR #65004: fix(cron): suppress trailing NO_REPLY in announce delivery path [AI-assisted]

Description (problem / solution / changelog)

Problem

Cron announce delivery sends the full agent response to the target channel when it contains NO_REPLY mixed with other text. The NO_REPLY suppression only works when it is the entire response.

Agents in cron/isolated sessions often emit analysis or summary text before appending NO_REPLY:

All 3 items already processed.

NO_REPLY

The delivery path in delivery-dispatch.ts uses isSilentReplyText() which requires an exact match (^\sNO_REPLY\s$), so this mixed text passes through and leaks to the user.

Root Cause

stripSilentToken() from okens.ts already handles trailing NO_REPLY removal, but was not called in the cron announce delivery path (delivery-dispatch.ts).

Fix

Use stripSilentToken() to detect trailing NO_REPLY tokens in both delivery code paths:

  1. deliverViaDirect (structured/direct path) — filter payloads by comparing text before and after stripping; suppress when different
  2. ** inalizeTextDelivery** (text-only path) — same comparison on synthesizedText

When the stripped result differs from the original, the payload carried a trailing NO_REPLY and delivery is suppressed via the existing inishSilentReplyDelivery() path.

Testing

Added 2 regression tests to delivery-dispatch.double-announce.test.ts:

  • Trailing NO_REPLY after summary in direct delivery → suppressed ✅
  • Trailing NO_REPLY after summary in text delivery → suppressed ✅

All 36 existing tests continue to pass (no regressions).

Fixes #64976

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts (modified, +46/-0)
  • src/cron/isolated-agent/delivery-dispatch.ts (modified, +23/-4)

PR #65016: Fix/cron announce no reply

Description (problem / solution / changelog)

Summary

  • Problem: Cron announce delivery only suppresses NO_REPLY when it is the exact/entire response (isSilentReplyText). When the agent prepends summary text before NO_REPLY, the full response is delivered to the target channel.
  • Why it matters: 14/15 observed production cron deliveries over 8 days contained NO_REPLY but were delivered anyway. Every user with announce-mode crons using NO_REPLY suppression hits this.
  • What changed: Ported the leading/embedded NO_REPLY token stripping from the auto-reply path (normalize-reply.ts, PR #63068) into the cron announce delivery path. Three locations: deliverViaDirect() and finalizeTextDelivery() in delivery-dispatch.ts, and runSubagentAnnounceFlow() in subagent-announce.ts.
  • What did NOT change (scope boundary): Existing isSilentReplyText exact-match checks are preserved. No changes to the auto-reply path, token definitions, or cron scheduling logic. No new functions — only imports of existing helpers.

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 #64976
  • Related #63068 (original NO_REPLY stripping in auto-reply path)
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: PR #63068 added startsWithSilentToken / stripLeadingSilentToken / stripSilentToken to the auto-reply path but the cron announce delivery path was not updated. It still uses only isSilentReplyText (exact match).
  • Missing detection / guardrail: No test covering NO_REPLY mixed with other text in cron announce delivery.
  • Contributing context (if known): Agents naturally summarize findings before appending NO_REPLY, making the exact-match check insufficient in practice.

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/cron/isolated-agent/delivery-dispatch.test.ts
  • Scenario the test should lock in: Agent response "All items processed.\n\nNO_REPLY" → delivery suppressed (not delivered to channel)
  • Why this is the smallest reliable guardrail: Unit test on the delivery filter pipeline catches the exact gap without needing a running gateway
  • Existing test that already covers this (if any): None for this path. Auto-reply path has coverage in normalize-reply.test.ts.
  • If no new test is added, why not: No node_modules installed locally to run the test suite. Happy to add a test if maintainers prefer — the pattern exists in normalize-reply.test.ts.

User-visible / Behavior Changes

Cron announce deliveries containing NO_REPLY mixed with other text will now be stripped or suppressed, matching the behavior of the direct-reply path. Users who previously received false deliveries with NO_REPLY embedded in the text will no longer receive them.

Diagram (if applicable)

Before:
[agent responds "Summary text\n\nNO_REPLY"] -> [isSilentReplyText: no match] -> [delivered to channel]

After:
[agent responds "Summary text\n\nNO_REPLY"] -> [stripSilentToken removes NO_REPLY] -> [text has content] -> [stripped text delivered]
[agent responds "NO_REPLY rest of text"] -> [stripLeadingSilentToken] -> [text has content] -> [stripped text delivered]
[agent responds "All done\n\nNO_REPLY"] -> [stripSilentToken] -> [only "All done" remains] -> [stripped text delivered]
[agent responds "NO_REPLY"] -> [isSilentReplyText: exact match] -> [suppressed, not delivered]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS 15.4
  • Runtime/container: Node 22 / npm global
  • Model/provider: anthropic/claude-opus-4
  • Integration/channel (if any): Discord announce delivery
  • Relevant config (redacted): Isolated cron with --announce --channel discord --to "channel:xxx"

Steps

  1. openclaw cron add --name "Test" --cron "*/5 * * * *" --session isolated --message "Check X. If nothing actionable, reply NO_REPLY" --announce --channel discord --to "channel:xxx"
  2. Agent responds with summary text followed by NO_REPLY
  3. Observe delivery behavior

Expected

  • Delivery suppressed when normalized response resolves to NO_REPLY

Actual

  • Full response including summary text delivered to channel

Evidence

  • Trace/log snippets

15 cron deliveries over 8 days from production instance, 14 contained NO_REPLY in the response text but were delivered to Discord/WhatsApp. Pattern: agent writes summary, appends NO_REPLY. Delivery system did not recognize it as suppression because isSilentReplyText only matches exact/whole-string.

Human Verification (required)

  • Verified scenarios: Confirmed isSilentReplyText is the only check in delivery-dispatch.ts and subagent-announce.ts. Confirmed startsWithSilentToken / stripLeadingSilentToken / stripSilentToken exist in src/auto-reply/tokens.ts and are not imported in the cron path. Verified the diff applies the same pattern as normalize-reply.ts.
  • Edge cases checked: Empty text after stripping routes to finishSilentReplyDelivery(). Payloads with content after stripping are preserved. Existing exact-match checks are not altered.
  • What you did not verify: Could not run type checker or test suite — no node_modules installed locally. Did not verify against a running gateway.

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
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Stripping NO_REPLY from mixed-content responses could remove the token but still deliver text the user intended to suppress entirely.
    • Mitigation: This matches the existing auto-reply path behavior (PR #63068). If the agent writes substantive text alongside NO_REPLY, delivering the substantive text (with the token stripped) is the safer default. Full suppression only occurs when the entire response is NO_REPLY.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/agents/subagent-announce.ts (modified, +48/-2)
  • src/auto-reply/reply/normalize-reply.ts (modified, +1/-1)
  • src/auto-reply/tokens.ts (modified, +1/-1)
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts (modified, +96/-1)
  • src/cron/isolated-agent/delivery-dispatch.ts (modified, +67/-28)

PR #65577: Outbound: centralize payload normalization semantics

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: outbound payload normalization semantics were duplicated across delivery prep, JSON output shaping, and mirror summary paths.
  • Why it matters: duplicated keep/drop and text/media parsing behavior increases recurring drift risk (for silent tokens, MEDIA: directives, reasoning suppression, and metadata preservation), which can cause regressions across channels and call sites.
  • What changed: introduced a single channel-agnostic OutboundPayloadPlan authority in src/infra/outbound/payloads.ts and migrated delivery/mirror/JSON call sites to consume plan projections.
  • What did NOT change (scope boundary): explicit-send literal behavior, queue raw persistence, adapter/channel transforms, heartbeat/cron semantics, and delivery transport ordering/side effects.

Background / Issue Signal

  • Recent issue signal for this hotspot included #65002, #64665, #64976, #65043, #64971, #65315, and #65375 (recurring outbound text/media/silent/routing drift across entry paths).
  • Hotspot evidence: src/infra/outbound/deliver.ts remains a large seam (~804 LOC), historically combining normalization concerns with transport concerns.
  • Cross-cutting semantics have broad fan-out (resolveSendableOutboundReplyParts appears in ~30 non-test usages, isSilentReplyText in ~21, and stripHeartbeatToken in ~11), which is where drift pressure comes from.
  • This PR centralizes outbound normalization at the outbound seam while intentionally keeping heartbeat/cron-specific semantics separate, consistent with the approved constraints.

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 #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: outbound normalization logic evolved in multiple places with overlapping responsibilities, so behavior updates could land in one path but not others.
  • Missing detection / guardrail: insufficient cross-path characterization tests tying delivery, mirror, and JSON behavior to the same expected semantics.
  • Contributing context (if known): multiple independent call sites (deliver.ts, send.ts, message.ts, agents/command/delivery.ts) each needed normalization outputs.

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/outbound/payloads.test.ts
    • src/infra/outbound/deliver.test.ts
    • src/infra/outbound/message.test.ts
  • Scenario the test should lock in:
    • silent token exact/mixed/JSON keep/drop semantics
    • media + silent combinations
    • directive parsing (MEDIA:, reply tags, audioAsVoice)
    • reasoning suppression
    • channelData-only handling
    • mirror parity and queue raw persistence
  • Why this is the smallest reliable guardrail: these tests assert behavior at the normalization boundary and at transport/mirror seams where drift previously occurred.
  • Existing test that already covers this (if any): existing outbound delivery/message/send tests were extended and retained.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

None intended. This is behavior-preserving centralization with stronger regression coverage.

Diagram (if applicable)

Before:
caller -> local normalization variant A/B/C -> delivery/json/mirror

After:
caller -> OutboundPayloadPlan (single normalization authority) -> projection (delivery/json/mirror)

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: macOS
  • Runtime/container: Node 24 + pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): outbound channel adapters (test doubles)
  • Relevant config (redacted): test configs only

Steps

  1. Run outbound normalization and seam tests.
  2. Verify queue write-ahead behavior remains raw.
  3. Verify mirror matrix behavior for MEDIA: and silent token variants.

Expected

  • Delivery/JSON/mirror semantics stay stable while normalization authority is centralized.

Actual

  • Matches expected in targeted suites.

Evidence

Attach at least one:

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

Key verification commands run:

  • pnpm test src/infra/outbound/payloads.test.ts src/infra/outbound/deliver.test.ts src/infra/outbound/message.test.ts src/gateway/server-methods/send.test.ts src/agents/command/delivery.test.ts
  • pnpm test src/infra/outbound/deliver.test.ts src/infra/outbound/message.test.ts

Human Verification (required)

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

  • Verified scenarios:
    • plan projections preserve existing normalization behavior for delivery/json/mirror
    • queue persistence stays raw at enqueue boundary
    • sendMessage mirror outcomes for directive/silent-token matrix remain as expected
    • delivery transport flow still runs queue/hook/adapter paths in existing order
  • Edge cases checked:
    • exact + JSON silent tokens
    • silent token with media
    • MEDIA: directive extraction
    • channelData-only payload behavior
  • What you did not verify:
    • full monorepo pnpm test green state (repository currently has unrelated failing shards outside this change surface)

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: subtle normalization drift in less-common payload combinations.
    • Mitigation: added characterization + matrix tests with fixed expected outputs at queue and mirror seams.
  • Risk: future expansion could accidentally move channel-specific logic into plan.
    • Mitigation: explicit plan boundary comments and keeping adapter transforms in deliver.ts.

Changed files

  • src/agents/command/delivery.ts (modified, +6/-4)
  • src/gateway/server-methods/send.ts (modified, +10/-13)
  • src/infra/outbound/deliver.test.ts (modified, +30/-0)
  • src/infra/outbound/deliver.ts (modified, +16/-24)
  • src/infra/outbound/message.test.ts (modified, +103/-0)
  • src/infra/outbound/message.ts (modified, +10/-10)
  • src/infra/outbound/payloads.test.ts (modified, +238/-0)
  • src/infra/outbound/payloads.ts (modified, +132/-54)

Code Example

{
  "ts": 1775912773688,
  "jobId": "ccabd2fc-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "action": "finished",
  "status": "ok",
  "summary": "All 3 returned items have the xxxxxx Action field filled:\n\n1. **Item A** — xxxxxx Action already written (details)\n2. **Item B** — xxxxx Action already written (details)\n3. **Item C** — xxxxxx Action already written (details)\n\nNO_REPLY",
  "runAtMs": 1775912739997,
  "durationMs": 33687,
  "nextRunAtMs": 1775930400000,
  "model": "claude-sonnet-4-6",
  "provider": "anthropic",
  "usage": {
    "input_tokens": 5,
    "output_tokens": 1590,
    "total_tokens": 29930
  },
  "delivered": true,
  "deliveryStatus": "delivered",
  "sessionId": "b5885ee6-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "sessionKey": "agent:main:cron:ccabd2fc-xxxx:run:b5885ee6-xxxx"
}


Key details for the bug report:
The summary field ends with \n\nNO_REPLY
Despite NO_REPLY being present, delivered: true and deliveryStatus: "delivered"
The cron prompt explicitly instructed: "if ALL items already actioned, reply with exactly NO_REPLY"
The agent prepended a summary before the NO_REPLY token
The direct-reply path strips "glued leading NO_REPLY tokens" (per 2026.4.9 changelog), but the cron announce delivery path doesn't apply equivalent normalization
This pattern repeated 14 times over 8 days (3x/day cron schedule, ~50% hit rate)
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

Cron announce delivery sends the full agent response when it contains NO_REPLY mixed with other text; the NO_REPLY suppression only works when it is the entire response.

Steps to reproduce

  1. openclaw cron add --name "Test" --cron "*/5 * * * *" --session isolated --message "Check X. If nothing actionable, reply NO_REPLY" --announce --channel discord --to "channel:xxx"
  2. Wait for the agent to respond with summary text followed by NO_REPLY (e.g., "All 3 items already processed.\n\nNO_REPLY")
  3. Observe that the full response including the summary is delivered to the target channel

Expected behavior

Delivery is suppressed when the normalized response resolves to NO_REPLY, consistent with the direct-reply path fix landed in 2026.4.9 ("strip glued leading NO_REPLY tokens").

Actual behavior

Full response including summary text before NO_REPLY is delivered to the announce target. 15 deliveries observed over 8 days from production crons, 14 contained NO_REPLY but were delivered anyway.

OpenClaw version

2026.4.8

Operating system

macOS 15.4

Install method

npm global

Model

anthropic/claude-opus-4 (isolated cron jobs)

Provider / routing chain

openclaw -> anthropic

Additional provider/model setup details

Opus for interactive, Sonnet for bulk crons

Logs, screenshots, and evidence

{
  "ts": 1775912773688,
  "jobId": "ccabd2fc-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "action": "finished",
  "status": "ok",
  "summary": "All 3 returned items have the xxxxxx Action field filled:\n\n1. **Item A** — xxxxxx Action already written (details)\n2. **Item B** — xxxxx Action already written (details)\n3. **Item C** — xxxxxx Action already written (details)\n\nNO_REPLY",
  "runAtMs": 1775912739997,
  "durationMs": 33687,
  "nextRunAtMs": 1775930400000,
  "model": "claude-sonnet-4-6",
  "provider": "anthropic",
  "usage": {
    "input_tokens": 5,
    "output_tokens": 1590,
    "total_tokens": 29930
  },
  "delivered": true,
  "deliveryStatus": "delivered",
  "sessionId": "b5885ee6-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "sessionKey": "agent:main:cron:ccabd2fc-xxxx:run:b5885ee6-xxxx"
}


Key details for the bug report:
The summary field ends with \n\nNO_REPLY
Despite NO_REPLY being present, delivered: true and deliveryStatus: "delivered"
The cron prompt explicitly instructed: "if ALL items already actioned, reply with exactly NO_REPLY"
The agent prepended a summary before the NO_REPLY token
The direct-reply path strips "glued leading NO_REPLY tokens" (per 2026.4.9 changelog), but the cron announce delivery path doesn't apply equivalent normalization
This pattern repeated 14 times over 8 days (3x/day cron schedule, ~50% hit rate)

Impact and severity

Affected: All users with announce-mode isolated crons using NO_REPLY suppression Severity: Annoying — causes false deliveries to user-facing channels Frequency: 14/15 observed cron deliveries over 8 days Consequence: Users receive noise messages that should have been suppressed; workaround requires adding fragile anti-reasoning prompt rules to every cron

Additional information

The NO_REPLY normalization already exists in the direct-reply auto-reply path (2026.4.9 release notes). The cron announce delivery path does not apply the same logic. Likely a 10-20 line fix to reuse the existing normalizer.

extent analysis

TL;DR

The cron announce delivery should be updated to apply the same NO_REPLY normalization logic as the direct-reply path to suppress deliveries when the response contains NO_REPLY mixed with other text.

Guidance

  • Review the direct-reply path's NO_REPLY normalization logic in the 2026.4.9 release to understand how it strips "glued leading NO_REPLY tokens".
  • Apply the same normalization logic to the cron announce delivery path to ensure consistent suppression of NO_REPLY responses.
  • Verify the fix by testing cron jobs with responses containing NO_REPLY mixed with other text to ensure deliveries are suppressed.
  • Consider adding automated tests to cover this scenario and prevent regressions.

Example

No code snippet is provided as the exact implementation details are not specified in the issue.

Notes

The fix is likely to be a small update to the cron announce delivery path, reusing the existing normalization logic from the direct-reply path.

Recommendation

Apply the workaround by updating the cron announce delivery path to apply the same NO_REPLY normalization logic as the direct-reply path, as this will provide a consistent and reliable solution to the issue.

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

Delivery is suppressed when the normalized response resolves to NO_REPLY, consistent with the direct-reply path fix landed in 2026.4.9 ("strip glued leading NO_REPLY tokens").

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]: (cron): announce delivery ignores NO_REPLY when agent prepends other text [3 pull requests, 1 comments, 2 participants]