openclaw - ✅(Solved) Fix [Feature]: Add additional targets that result of cron jobs will be sent to multiple channels [1 pull requests, 1 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#54935Fetched 2026-04-08 01:34:22
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
closed ×1cross-referenced ×1labeled ×1locked ×1

Add new delivery.additionalTargets to Cron job, so the result of Cron jobs will be set to multiple channels.

Root Cause

Add new delivery.additionalTargets to Cron job, so the result of Cron jobs will be set to multiple channels.

Fix Action

Fix / Workaround

Added additionalTargets field to CronDelivery — an array of { channel, to, accountId? } entries that receive the same payloads after the primary delivery succeeds. Each target is resolved and delivered independently; one failure does not block the others. Added deliverToAdditionalTargets() in src/cron/isolated-agent/delivery-dispatch.ts and wired it into the cron run pipeline in src/cron/isolated-agent/run.ts. Added normalization and validation of additionalTargets in src/cron/normalize.ts (trims/lowercases channel, trims to, strips blank accountId, filters out entries missing either required field). Imported buildAgentSessionKey, resolveThreadSessionKeys, and RoutePeer from openclaw/plugin-sdk/routing into extensions/discord/src/channel.ts to fix outbound session-key construction for Discord. Changed channelRuntime in src/auto-reply/reply/commands-session.ts from an eager module-level constant to a lazy getChannelRuntime() accessor, preventing plugin runtime initialization at import time.

PR fix notes

PR #44859: Cron: Add additional targets that result of cron jobs will be sent to multiple channels

Description (problem / solution / changelog)

Summary

  • Problem: Cron jobs could only deliver their output to a single channel/recipient; there was no built-in way to fan-out the same result to multiple destinations simultaneously. Additionally, the Discord channel plugin was missing session-routing utilities needed for correct outbound session-key resolution, and commands-session.ts eagerly initialized the plugin runtime at module load time, which could crash at startup when channel plugins were not yet available.
  • Why it matters: Teams that monitor activity across multiple channels (e.g. Telegram + Discord + Signal) had to create duplicate cron jobs for each destination, duplicating configuration and increasing scheduling overhead.
  • What changed:
    • Added additionalTargets field to CronDelivery — an array of { channel, to, accountId? } entries that receive the same payloads after the primary delivery succeeds. Each target is resolved and delivered independently; one failure does not block the others.
    • Added deliverToAdditionalTargets() in src/cron/isolated-agent/delivery-dispatch.ts and wired it into the cron run pipeline in src/cron/isolated-agent/run.ts.
    • Added normalization and validation of additionalTargets in src/cron/normalize.ts (trims/lowercases channel, trims to, strips blank accountId, filters out entries missing either required field).
    • Imported buildAgentSessionKey, resolveThreadSessionKeys, and RoutePeer from openclaw/plugin-sdk/routing into extensions/discord/src/channel.ts to fix outbound session-key construction for Discord.
    • Changed channelRuntime in src/auto-reply/reply/commands-session.ts from an eager module-level constant to a lazy getChannelRuntime() accessor, preventing plugin runtime initialization at import time.
  • What did NOT change: Primary delivery behavior, failure-destination logic, cron scheduling, and all other channel plugins are unmodified.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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

  • Related #

AI/Vibe-Coded PRs

  • Mark as AI-assisted in the PR title or description
  • Note the degree of testing : fully tested
  • Confirm you understand what the code does: Yes

User-visible / Behavior Changes

New delivery.additionalTargets field for cron jobs. After the primary delivery completes, the same output payloads are sent to each additional target. Example:

"delivery": {
  "mode": "announce",
  "channel": "telegram",
  "to": "123456789",
  "additionalTargets": [
    { "channel": "signal", "to": "+15550001111" },
    { "channel": "discord", "to": "channel-456", "accountId": "bot-b" }
  ]
}
  • Fan-out is best-effort per target: a failure on one target is logged and skipped; remaining targets still receive delivery.
  • bestEffort from the primary delivery is inherited by all additional targets.
  • Invalid entries (empty channel or to, blank accountId) are silently filtered at normalization time.

Security Impact (required)

  • New permissions/capabilities? Yes — cron jobs can now deliver to additional channel targets, increasing the outbound delivery surface.
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes — each additional target results in an outbound messaging call using the existing deliverOutboundPayloads path, which already applies SSRF and channel-allowlist policies.
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: Fan-out reuses the existing resolveDeliveryTarget + deliverOutboundPayloads stack, so all existing allowlist, SSRF, and auth checks apply to each additional target identically. No new bypass paths are introduced.

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node 22 / Bun
  • Model/provider: any
  • Integration/channel: Telegram, Signal, Discord
  • Relevant config (redacted): delivery.additionalTargets in a cron job

Steps

  1. Create a cron job with delivery.mode = "announce", a primary channel/to, and an additionalTargets array containing two entries (e.g. Signal + Discord).
  2. Trigger the job manually or wait for its scheduled run.
  3. Confirm the primary channel receives the output.
  4. Confirm each additional target also receives the same output.
  5. Disable one additional target's channel to confirm the other target still receives delivery and the job does not fail.

Expected

  • Primary delivery succeeds.
  • Each additional target independently receives the same payloads.
  • A failing additional target logs a warning and does not affect other targets or the run outcome.

Actual

  • Matches expected in all three cases.

Evidence

  • Failing test/log before + passing after — new unit tests in src/cron/isolated-agent/delivery-dispatch.additional-targets.test.ts cover: successful fan-out, target-resolution failure isolation, delivery failure isolation, accountId forwarding, empty targets array, and bestEffort flag propagation.
  • Normalization tests in src/cron/normalize.test.ts cover: valid targets, casing/trimming, invalid-entry filtering, empty-array cleanup, and blank accountId stripping.

Human Verification (required)

  • Verified scenarios: fan-out to two additional targets (Telegram + Signal); one target failing does not block the other; bestEffort is correctly inherited.
  • Edge cases checked: empty additionalTargets array is treated as no-op; targets with blank accountId are normalized correctly; primary delivery failure prevents fan-out (fan-out runs only after primary succeeds).
  • What you did not verify: webhook-mode additional targets; more than three simultaneous additional targets under real network load.

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? YesadditionalTargets is optional; existing cron jobs without it are unaffected.
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Remove additionalTargets from the affected cron job's delivery config via openclaw cron edit <jobId>.
  • Files/config to restore: src/cron/types.ts, src/cron/normalize.ts, src/cron/isolated-agent/delivery-dispatch.ts, src/cron/isolated-agent/run.ts.
  • Known bad symptoms reviewers should watch for: [additional-delivery] warn lines appearing in gateway logs for targets that were expected to succeed; primary delivery being blocked (should not happen — fan-out is always fire-and-continue).

Risks and Mitigations

  • Risk: A misconfigured additionalTargets entry silently fails without surfacing in the job's lastRunStatus.
    • Mitigation: Failures are logged at WARN level with the [additional-delivery] prefix and the target channel/address. A follow-up could surface per-target delivery status in openclaw cron list output.
  • Risk: High-frequency cron jobs with many additional targets could increase outbound message volume significantly.
    • Mitigation: This is intentional behavior; operators control the target list. Rate-limit and allowlist policies already apply via deliverOutboundPayloads.

Closes #48026 Closes #54935

Changed files

  • src/auto-reply/reply/commands-session.ts (modified, +9/-10)
  • src/cli/cron-cli.test.ts (modified, +216/-8)
  • src/cli/cron-cli/register.cron-edit.ts (modified, +89/-0)
  • src/cron/delivery.test.ts (modified, +87/-0)
  • src/cron/delivery.ts (modified, +35/-1)
  • src/cron/isolated-agent/delivery-dispatch.additional-targets.test.ts (added, +249/-0)
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts (modified, +127/-0)
  • src/cron/isolated-agent/delivery-dispatch.ts (modified, +110/-1)
  • src/cron/isolated-agent/run.ts (modified, +38/-6)
  • src/cron/normalize.test.ts (modified, +130/-0)
  • src/cron/normalize.ts (modified, +31/-0)
  • src/cron/service/jobs.ts (modified, +19/-1)
  • src/cron/types.ts (modified, +8/-0)
  • src/gateway/protocol/schema/cron.ts (modified, +10/-0)
RAW_BUFFERClick to expand / collapse

Summary

Add new delivery.additionalTargets to Cron job, so the result of Cron jobs will be set to multiple channels.

Problem to solve

Cron jobs could only deliver their output to a single channel/recipient; there was no built-in way to fan-out the same result to multiple destinations simultaneously. Additionally, the Discord channel plugin was missing session-routing utilities needed for correct outbound session-key resolution, and commands-session.ts eagerly initialized the plugin runtime at module load time, which could crash at startup when channel plugins were not yet available.

Proposed solution

Added additionalTargets field to CronDelivery — an array of { channel, to, accountId? } entries that receive the same payloads after the primary delivery succeeds. Each target is resolved and delivered independently; one failure does not block the others. Added deliverToAdditionalTargets() in src/cron/isolated-agent/delivery-dispatch.ts and wired it into the cron run pipeline in src/cron/isolated-agent/run.ts. Added normalization and validation of additionalTargets in src/cron/normalize.ts (trims/lowercases channel, trims to, strips blank accountId, filters out entries missing either required field). Imported buildAgentSessionKey, resolveThreadSessionKeys, and RoutePeer from openclaw/plugin-sdk/routing into extensions/discord/src/channel.ts to fix outbound session-key construction for Discord. Changed channelRuntime in src/auto-reply/reply/commands-session.ts from an eager module-level constant to a lazy getChannelRuntime() accessor, preventing plugin runtime initialization at import time.

Impact

  • Affected channels
  • Severity: Teams that monitor activity across multiple channels (e.g. Telegram + Discord + Signal) had to create duplicate cron jobs for each destination, duplicating configuration and increasing scheduling overhead.
  • Frequency: always
  • Consequence: extra manual work

Evidence/examples

"delivery": { "mode": "announce", "channel": "telegram", "to": "123456789", "additionalTargets": [ { "channel": "signal", "to": "+15550001111" }, { "channel": "discord", "to": "channel-456", "accountId": "bot-b" } ] }

Additional information

For more detail, refer to PR: https://github.com/openclaw/openclaw/pull/44859

extent analysis

Fix Plan

To implement the solution, follow these steps:

  • Add an additionalTargets field to the CronDelivery object, which is an array of objects containing channel, to, and optional accountId properties.
  • Implement the deliverToAdditionalTargets() function in src/cron/isolated-agent/delivery-dispatch.ts to handle delivery to multiple targets.
  • Update the cron run pipeline in src/cron/isolated-agent/run.ts to call deliverToAdditionalTargets() after primary delivery succeeds.
  • Add normalization and validation for additionalTargets in src/cron/normalize.ts to ensure correct formatting and required fields.

Example code for deliverToAdditionalTargets():

async function deliverToAdditionalTargets(delivery: CronDelivery) {
  const additionalTargets = delivery.additionalTargets;
  if (!additionalTargets) return;

  for (const target of additionalTargets) {
    try {
      // Resolve and deliver to each target independently
      const channel = target.channel;
      const to = target.to;
      const accountId = target.accountId;
      // Call the delivery function for each target
      await deliverToTarget(channel, to, accountId);
    } catch (error) {
      // Log the error and continue to the next target
      console.error(`Error delivering to target: ${error}`);
    }
  }
}

Example code for normalization and validation in src/cron/normalize.ts:

function normalizeAdditionalTargets(additionalTargets: any[]) {
  return additionalTargets.map((target) => {
    return {
      channel: target.channel.trim().toLowerCase(),
      to: target.to.trim(),
      accountId: target.accountId ? target.accountId.trim() : undefined,
    };
  }).filter((target) => target.channel && target.to);
}

Verification

To verify the fix, create a cron job with multiple additionalTargets and check that the delivery is successful for each target. You can use the example delivery object provided in the issue body as a test case.

Extra Tips

  • Make sure to handle errors and exceptions properly when delivering to multiple targets to prevent blocking or crashing the cron job.
  • Consider adding logging and monitoring to track the success and failure of deliveries to each target.

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 - ✅(Solved) Fix [Feature]: Add additional targets that result of cron jobs will be sent to multiple channels [1 pull requests, 1 participants]