openclaw - 💡(How to fix) Fix Cron announce delivery strips plugin-canonical provider prefix after target resolution

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…

Cron announce delivery can corrupt channel-specific canonical targets by stripping the selected provider prefix after the channel/plugin target resolver has already resolved the target.

This breaks channels whose canonical target grammar intentionally includes the provider/channel prefix as part of the target ID.

Observed with a bncr channel target:

configured delivery.to: Bncr:tgBot:-1003891624016:6278285192
resolved delivery.to:   tgBot:-1003891624016:6278285192
send failure: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>)

The cron job itself completed successfully, but the final announce delivery failed, so the run was marked as error and repeated hourly jobs started accumulating failures/backoff.

Error Message

{ "delivery": { "intended": { "channel": "bncr", "to": "Bncr:tgBot:-1003891624016:6278285192", "source": "explicit" }, "resolved": { "ok": true, "channel": "bncr", "to": "tgBot:-1003891624016:6278285192", "source": "explicit" }, "fallbackUsed": true, "delivered": false }, "error": "OutboundDeliveryError: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>): tgBot:-1003891624016:6278285192" }

Root Cause

Cron announce delivery can corrupt channel-specific canonical targets by stripping the selected provider prefix after the channel/plugin target resolver has already resolved the target.

This breaks channels whose canonical target grammar intentionally includes the provider/channel prefix as part of the target ID.

Observed with a bncr channel target:

configured delivery.to: Bncr:tgBot:-1003891624016:6278285192
resolved delivery.to:   tgBot:-1003891624016:6278285192
send failure: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>)

The cron job itself completed successfully, but the final announce delivery failed, so the run was marked as error and repeated hourly jobs started accumulating failures/backoff.

Fix Action

Fix / Workaround

A channel plugin can defensively accept the stripped bare target form, but that seems like a workaround rather than the right boundary. The host has already asked the channel resolver; once a resolver returns a canonical target, the host should preserve it unless the plugin explicitly opts into generic stripping.

Code Example

configured delivery.to: Bncr:tgBot:-1003891624016:6278285192
resolved delivery.to:   tgBot:-1003891624016:6278285192
send failure: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>)

---

{
  "delivery": {
    "intended": {
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "resolved": {
      "ok": true,
      "channel": "bncr",
      "to": "tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "fallbackUsed": true,
    "delivered": false
  },
  "error": "OutboundDeliveryError: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>): tgBot:-1003891624016:6278285192"
}

---

{
  "delivery": {
    "intended": {
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "resolved": {
      "ok": true,
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "delivered": true
  }
}

---

cron delivery
resolveCronAnnounceDelivery()
resolveDeliveryTarget()
resolveChannelTargetForDelivery()
resolveChannelTarget()
→ plugin normalizeTarget / targetResolver hooks may participate
stripSelectedProviderPrefix()
sendDurableMessageBatch()

---

const targetResolution = await deliveryTargetRuntime.resolveChannelTargetForDelivery({
  cfg,
  channel,
  input: toCandidate,
  accountId,
});

resolvedTarget = targetResolution.target;

const selectedTarget = stripSelectedProviderPrefix({
  channel,
  to: resolvedTarget.to,
});

---

channel = bncr
to      = Bncr:tgBot:-1003891624016:6278285192

---

Bncr:tgBot:-1003891624016:6278285192

---

<provider>:<target>

---

Bncr:<platform>:<groupId>:<userId>

---

strip only for generic normalized/unknown fallback targets
preserve for plugin/directory/route canonical targets

---

messaging.targetResolver.preserveProviderPrefix = true

---

const shouldStripProviderPrefix =
  resolvedTarget.source === "normalized" &&
  !targetWasResolvedByPlugin;

const selectedTarget = shouldStripProviderPrefix
  ? stripSelectedProviderPrefix({ channel, to: resolvedTarget.to })
  : resolvedTarget.to;
RAW_BUFFERClick to expand / collapse

Summary

Cron announce delivery can corrupt channel-specific canonical targets by stripping the selected provider prefix after the channel/plugin target resolver has already resolved the target.

This breaks channels whose canonical target grammar intentionally includes the provider/channel prefix as part of the target ID.

Observed with a bncr channel target:

configured delivery.to: Bncr:tgBot:-1003891624016:6278285192
resolved delivery.to:   tgBot:-1003891624016:6278285192
send failure: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>)

The cron job itself completed successfully, but the final announce delivery failed, so the run was marked as error and repeated hourly jobs started accumulating failures/backoff.

Impact

  • Cron jobs using delivery.mode=announce may fail even when the job body succeeds.
  • delivery.to can be transformed from a plugin-canonical target into a bare target that the channel plugin rejects.
  • Runs are recorded as failed due to delivery only, which can make users think the cron job itself did not run.
  • Repeated jobs may accumulate consecutive errors/backoff.
  • The issue is not limited to bncr conceptually; any channel whose canonical target includes a provider-like prefix can be affected.

Evidence / observed run state

Recent cron run recorded:

{
  "delivery": {
    "intended": {
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "resolved": {
      "ok": true,
      "channel": "bncr",
      "to": "tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "fallbackUsed": true,
    "delivered": false
  },
  "error": "OutboundDeliveryError: bncr invalid target(standard: Bncr:<platform>:<groupId>:<userId> | Bncr:<platform>:<userId>): tgBot:-1003891624016:6278285192"
}

Earlier successful runs for the same job/target resolved to the full canonical target:

{
  "delivery": {
    "intended": {
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "resolved": {
      "ok": true,
      "channel": "bncr",
      "to": "Bncr:tgBot:-1003891624016:6278285192",
      "source": "explicit"
    },
    "delivered": true
  }
}

Relevant code path

In the built package this appears to be the relevant path:

cron delivery
→ resolveCronAnnounceDelivery()
→ resolveDeliveryTarget()
→ resolveChannelTargetForDelivery()
→ resolveChannelTarget()
→ plugin normalizeTarget / targetResolver hooks may participate
→ stripSelectedProviderPrefix()
→ sendDurableMessageBatch()

In src/cron/isolated-agent/delivery-target.ts / built dist/jobs-*.js, the current ordering is effectively:

const targetResolution = await deliveryTargetRuntime.resolveChannelTargetForDelivery({
  cfg,
  channel,
  input: toCandidate,
  accountId,
});

resolvedTarget = targetResolution.target;

const selectedTarget = stripSelectedProviderPrefix({
  channel,
  to: resolvedTarget.to,
});

stripSelectedProviderPrefix() calls stripTargetProviderPrefix(trimmed, params.channel), so with:

channel = bncr
to      = Bncr:tgBot:-1003891624016:6278285192

it treats Bncr: as just the selected provider prefix and strips it.

Why this looks wrong

OpenClaw already has plugin participation points:

  • messaging.normalizeTarget
  • messaging.targetResolver.looksLikeId
  • messaging.targetResolver.resolveTarget
  • messaging.resolveOutboundSessionRoute

If a channel/plugin resolver has already produced a resolved/canonical target, cron delivery should not blindly apply generic provider-prefix stripping to that canonical value.

For some channels, a prefix that looks like the channel/provider selector is also part of the channel's own canonical target grammar.

In this case:

Bncr:tgBot:-1003891624016:6278285192

is not merely:

<provider>:<target>

It is the plugin's canonical target format:

Bncr:<platform>:<groupId>:<userId>

Expected behavior

Prefer plugin-resolved canonical targets over generic prefix stripping.

Suggested behavior:

  1. Determine the delivery channel.
  2. Pass the raw/explicit delivery.to through the channel target resolver/normalizer.
  3. If the plugin resolver returns a target, treat resolvedTarget.to as canonical and send that unchanged.
  4. Only apply generic provider-prefix stripping when the plugin did not resolve/claim the target and the system is falling back to generic normalized target handling.

Alternatively, make the stripping conditional on target source, e.g.:

strip only for generic normalized/unknown fallback targets
preserve for plugin/directory/route canonical targets

or add a channel/plugin capability flag such as:

messaging.targetResolver.preserveProviderPrefix = true

so channels can explicitly opt out when their canonical target grammar includes the provider prefix.

Suggested fix point

In cron delivery target resolution, avoid calling stripSelectedProviderPrefix() unconditionally on resolvedTarget.to.

Potential safer logic:

const shouldStripProviderPrefix =
  resolvedTarget.source === "normalized" &&
  !targetWasResolvedByPlugin;

const selectedTarget = shouldStripProviderPrefix
  ? stripSelectedProviderPrefix({ channel, to: resolvedTarget.to })
  : resolvedTarget.to;

The exact condition may need to match the existing target resolver source taxonomy, but the key point is: do not post-process a plugin-canonical target with generic provider-prefix stripping.

Notes

A channel plugin can defensively accept the stripped bare target form, but that seems like a workaround rather than the right boundary. The host has already asked the channel resolver; once a resolver returns a canonical target, the host should preserve it unless the plugin explicitly opts into generic stripping.

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

Prefer plugin-resolved canonical targets over generic prefix stripping.

Suggested behavior:

  1. Determine the delivery channel.
  2. Pass the raw/explicit delivery.to through the channel target resolver/normalizer.
  3. If the plugin resolver returns a target, treat resolvedTarget.to as canonical and send that unchanged.
  4. Only apply generic provider-prefix stripping when the plugin did not resolve/claim the target and the system is falling back to generic normalized target handling.

Alternatively, make the stripping conditional on target source, e.g.:

strip only for generic normalized/unknown fallback targets
preserve for plugin/directory/route canonical targets

or add a channel/plugin capability flag such as:

messaging.targetResolver.preserveProviderPrefix = true

so channels can explicitly opt out when their canonical target grammar includes the provider prefix.

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 Cron announce delivery strips plugin-canonical provider prefix after target resolution