Skip to content

fix(cron): deliver full announce output instead of last chunk only#57322

Merged
hydro13 merged 1 commit intoopenclaw:mainfrom
hydro13:fix/cron-announce-full-output
Mar 29, 2026
Merged

fix(cron): deliver full announce output instead of last chunk only#57322
hydro13 merged 1 commit intoopenclaw:mainfrom
hydro13:fix/cron-announce-full-output

Conversation

@hydro13
Copy link
Copy Markdown
Contributor

@hydro13 hydro13 commented Mar 29, 2026

Summary

  • resolveCronPayloadOutcome() collapsed announce delivery to the last deliverable payload
  • Replace with pickDeliverablePayloads() that preserves all successful text payloads
  • Error-only runs fall back to the last error payload only (no spam)
  • deliveryPayloadHasStructuredContent stays scoped to the last payload (preserves downstream safeguards)
  • Extract shared isDeliverablePayload() helper

Root Cause

pickLastDeliverablePayload() in src/cron/isolated-agent/helpers.ts returned only one payload. When a cron job produced multiple text chunks, only the last survived. Telegram forum topics exposed this directly because they use the direct-delivery path.

Change Type

  • Bug fix

Testing

73 test suites, 557 tests pass in src/cron/. Typecheck (pnpm tsgo) clean. New regression tests:

  • Multi-payload success delivery
  • Mixed success/error (errors filtered, success preserved)
  • All-error fallback (only last error delivered)
  • Structured content flag scoped to last payload

Fixes #13812

@openclaw-barnacle openclaw-barnacle bot added size: S maintainer Maintainer-authored PR labels Mar 29, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR fixes a bug where resolveCronPayloadOutcome() collapsed all deliverable payloads down to the last one, causing multi-chunk cron reports to deliver only the final chunk. The fix introduces pickDeliverablePayloads() — which preserves all successful (non-error) deliverable payloads — and falls back to the last error payload when no successful payload exists. The deliveryPayload field (used for deliveryPayloadHasStructuredContent routing) is deliberately kept scoped to the last payload to preserve existing downstream safeguards in delivery-dispatch.ts.

  • Extracts shared isDeliverablePayload() helper from the inline lambda in pickLastDeliverablePayload
  • Adds pickDeliverablePayloads() returning all successful deliverable payloads, with error-only fallback to a single last-error payload
  • Updates resolveCronPayloadOutcome() to populate deliveryPayloads via the new helper while keeping deliveryPayload (and deliveryPayloadHasStructuredContent) scoped to the last payload
  • Comprehensive regression tests added at the unit, helper, and integration levels
  • Test helper assertExplicitTelegramTargetDelivery correctly updated from single expectedText to expectedTexts[]

Confidence Score: 5/5

Safe to merge — the fix is well-scoped, correctly handles all identified edge cases, and is backed by thorough regression tests covering multi-payload success, mixed success/error, and all-error fallback scenarios.

All remaining observations are P2 or lower. The core logic in pickDeliverablePayloads is correct: successful non-error deliverables are preserved in order; when none exist, the single last-error fallback prevents notification spam. The deliveryPayloadHasStructuredContent scoping is explicitly intentional per the PR description and tested. No logic bugs, data-loss paths, or security concerns were identified.

No files require special attention.

Important Files Changed

Filename Overview
src/cron/isolated-agent/helpers.ts Extracts isDeliverablePayload helper, adds pickDeliverablePayloads returning all successful deliverable payloads with error-only fallback, and updates resolveCronPayloadOutcome to use it. Logic is correct for all identified edge cases.
src/cron/isolated-agent/helpers.test.ts New pickDeliverablePayloads describe block covers multi-payload success and all-errors fallback. Tests are well-structured and cover the key scenarios.
src/cron/isolated-agent.helpers.test.ts Three new integration-level tests for resolveCronPayloadOutcome: multi-payload success, structured-content scoping to last payload, and all-error fallback. All correctly reflect intended behaviour.
src/cron/isolated-agent.direct-delivery-forum-topics.test.ts New integration test verifying that all successful text chunks are delivered independently to Telegram forum-topic targets (the original bug surface). Error payloads in the middle are correctly filtered.
src/cron/isolated-agent.skips-delivery-without-whatsapp-recipient-besteffortdeliver-true.test.ts Test helper assertExplicitTelegramTargetDelivery updated from single expectedText to expectedTexts[], with branched assertion logic for single vs. multi-message calls. Existing tests updated to use arrays.

Reviews (1): Last reviewed commit: "fix(cron): deliver full announce output ..." | Re-trigger Greptile

Copy link
Copy Markdown

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

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: 1af36fafb8

ℹ️ 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".

@hydro13 hydro13 force-pushed the fix/cron-announce-full-output branch 2 times, most recently from a937c5b to 71d471f Compare March 29, 2026 22:57
Copy link
Copy Markdown

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

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: 71d471f713

ℹ️ 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".

@hydro13 hydro13 force-pushed the fix/cron-announce-full-output branch from 71d471f to a31659b Compare March 29, 2026 23:14
@hydro13 hydro13 self-assigned this Mar 29, 2026
resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.

Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.

Fixes openclaw#13812
@hydro13 hydro13 force-pushed the fix/cron-announce-full-output branch from a31659b to 779d2ce Compare March 29, 2026 23:24
@hydro13 hydro13 merged commit bdd9bc9 into openclaw:main Mar 29, 2026
8 checks passed
@hydro13
Copy link
Copy Markdown
Contributor Author

hydro13 commented Mar 29, 2026

Merged via squash.

  • Merge SHA: bdd9bc9
  • Added pickDeliverablePayloads() to preserve all successful text payloads for announce delivery
  • Error-only runs fall back to last error payload only
  • Extracted shared isDeliverablePayload() helper
  • deliveryPayloadHasStructuredContent stays scoped to last payload
  • CHANGELOG entry added
  • 557 cron tests passing, typecheck clean

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 30, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded outbound message sending due to delivering all successful payloads in cron announce flow
Vulnerabilities

1. 🟡 Unbounded outbound message sending due to delivering all successful payloads in cron announce flow

Property Value
Severity Medium
CWE CWE-400
Location src/cron/isolated-agent/helpers.ts:100-109

Description

resolveCronPayloadOutcome() now returns all successful deliverable payloads via pickDeliverablePayloads() instead of only the final deliverable payload. Downstream, dispatchCronDelivery() passes deliveryPayloads to deliverOutboundPayloads(), which iterates and sends each payload (and may further chunk each payload into multiple messages).

Security impact:

  • Message flooding / cost DoS: if a run produces many deliverable payloads, the announce delivery path will send a corresponding number of outbound messages. There is no upper bound on payload count before the send loop.
  • Amplification: even a single large text payload can be chunked into multiple messages; multiple payloads multiplies this effect.

Vulnerable code (behavioral change enabling unbounded sends):

export function pickDeliverablePayloads(payloads: DeliveryPayload[]): DeliveryPayload[] {
  const successfulDeliverablePayloads = payloads.filter(
    (payload) => payload != null && payload.isError !== true && isDeliverablePayload(payload),
  );
  if (successfulDeliverablePayloads.length > 0) {
    return successfulDeliverablePayloads;
  }
  const lastDeliverablePayload = pickLastDeliverablePayload(payloads);
  return lastDeliverablePayload ? [lastDeliverablePayload] : [];
}

While payload generation is typically internal, it can often be influenced by prompts, retrieved content, tools, or upstream inputs. Without a cap, a malicious or malformed input that increases the number of produced payloads can cause outbound spam/rate-limit exhaustion and increased delivery costs.

Recommendation

Introduce an explicit cap/aggregation strategy for cron announce delivery payloads.

Options (pick one or combine):

  1. Send only the final payload (restore prior behavior) unless explicitly configured to stream:
const deliveryPayload = pickLastDeliverablePayload(payloads);
const deliveryPayloads = deliveryPayload ? [deliveryPayload] : [];
  1. Cap the number of payloads and/or collapse them into one message:
const MAX_ANNOUNCE_PAYLOADS = 3;
const selected = pickDeliverablePayloads(payloads).slice(-MAX_ANNOUNCE_PAYLOADS);// Optionally merge text-only payloads
const mergedText = selected.map(p => (p.text ?? "").trim()).filter(Boolean).join("\n\n");
const deliveryPayloads = mergedText ? [{ text: mergedText }] : selected;
  1. Add per-run delivery limits (max messages / max total chars) in the delivery dispatch layer before calling deliverOutboundPayloads().

Also consider applying stricter rules for what counts as deliverable in announce mode (e.g., exclude interim/status updates) to reduce inadvertent flooding.


Analyzed PR: #57322 at commit 779d2ce

Last updated on: 2026-03-30T09:44:35Z

pritchie pushed a commit to pritchie/openclaw that referenced this pull request Mar 30, 2026
…penclaw#57322)

resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.

Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.

Fixes openclaw#13812
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
…penclaw#57322)

resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.

Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.

Fixes openclaw#13812
livingghost pushed a commit to livingghost/openclaw that referenced this pull request Mar 31, 2026
…penclaw#57322)

resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.

Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.

Fixes openclaw#13812
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…penclaw#57322)

resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.

Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.

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

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: v2026.2.9 cron announce → Telegram topic sends summary instead of full output

1 participant