fix(cron): restore direct fallback after announce failure in best-effort mode (openclaw#36177)

Verified:
- pnpm build
- pnpm check (fails on pre-existing origin/main lint debt in extensions/mattermost imports)
- pnpm test:macmini

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Tak Hoffman
2026-03-05 07:25:24 -06:00
committed by GitHub
parent 4dc0c66399
commit 544abc927f
3 changed files with 35 additions and 35 deletions

View File

@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
- Security/dependency audit: patch transitive Hono vulnerabilities by pinning `hono` to `4.12.5` and `@hono/node-server` to `1.19.10` in production resolution paths. Thanks @shakkernerd.
- Security/dependency audit: bump `tar` to `7.5.10` (from `7.5.9`) to address the high-severity hardlink path traversal advisory (`GHSA-qffp-2rhf-9h96`). Thanks @shakkernerd.
- Cron/announce delivery robustness: bypass pending-descendant announce guards for cron completion sends, ensure named-agent announce routes have outbound session entries, and fall back to direct delivery only when an announce send was actually attempted and failed. (from #35185, #32443, #34987) Thanks @Sid-Qin, @scoootscooob, and @bmendonca3.
- Cron/announce best-effort fallback: run direct outbound fallback after attempted announce failures even when delivery is configured as best-effort, so Telegram cron sends are not left as attempted-but-undelivered after `cron announce delivery failed` warnings.
- Auto-reply/system events: restore runtime system events to the message timeline (`System:` lines), preserve think-hint parsing with prepended events, and carry events into deferred followup/collect/steer-backlog prompts to keep cache behavior stable without dropping queued metadata. (#34794) Thanks @anisoptera.
- Security/audit account handling: avoid prototype-chain account IDs in audit validation by using own-property checks for `accounts`. (#34982) Thanks @HOYALIM.
- Cron/restart catch-up semantics: replay interrupted recurring jobs and missed immediate cron slots on startup without replaying interrupted one-shot jobs, with guarded missed-slot probing to avoid malformed-schedule startup aborts and duplicate-trigger drift after restart. (from #34466, #34896, #34625, #33206) Thanks @dunamismax, @dsantoreis, @Octane0411, and @Sid-Qin.

View File

@@ -421,13 +421,13 @@ describe("runCronIsolatedAgentTurn", () => {
});
});
it("marks attempted when announce delivery reports false and best-effort is enabled", async () => {
it("falls back to direct delivery when announce reports false and best-effort is enabled", async () => {
const { res, deps } = await runAnnounceFlowResult(true);
expect(res.status).toBe("ok");
expect(res.delivered).toBe(false);
expect(res.delivered).toBe(true);
expect(res.deliveryAttempted).toBe(true);
expect(runSubagentAnnounceFlow).toHaveBeenCalledTimes(1);
expect(deps.sendMessageTelegram).not.toHaveBeenCalled();
expect(deps.sendMessageTelegram).toHaveBeenCalledTimes(1);
});
it("falls back to direct delivery when announce flow throws and best-effort is disabled", async () => {

View File

@@ -465,39 +465,38 @@ export async function dispatchCronDelivery(
}
} else {
const announceResult = await deliverViaAnnounce(params.resolvedDelivery);
if (announceResult) {
// Fall back to direct delivery only when the announce send was
// actually attempted and failed. Early returns from
// deliverViaAnnounce (active subagents, interim suppression,
// SILENT_REPLY_TOKEN) are intentional suppressions that must NOT
// trigger direct delivery — doing so would bypass the suppression
// guard and leak partial/stale content to the channel. (#32432)
if (announceDeliveryWasAttempted && !delivered && !params.isAborted()) {
const directFallback = await deliverViaDirect(params.resolvedDelivery);
if (directFallback) {
return {
result: directFallback,
delivered,
deliveryAttempted,
summary,
outputText,
synthesizedText,
deliveryPayloads,
};
}
// If direct delivery succeeded (returned null without error),
// `delivered` has been set to true by deliverViaDirect.
if (delivered) {
return {
delivered,
deliveryAttempted,
summary,
outputText,
synthesizedText,
deliveryPayloads,
};
}
// Fall back to direct delivery only when the announce send was actually
// attempted and failed. Early returns from deliverViaAnnounce (active
// subagents, interim suppression, SILENT_REPLY_TOKEN) are intentional
// suppressions that must NOT trigger direct delivery — doing so would
// bypass the suppression guard and leak partial/stale content.
if (announceDeliveryWasAttempted && !delivered && !params.isAborted()) {
const directFallback = await deliverViaDirect(params.resolvedDelivery);
if (directFallback) {
return {
result: directFallback,
delivered,
deliveryAttempted,
summary,
outputText,
synthesizedText,
deliveryPayloads,
};
}
// If direct delivery succeeded (returned null without error),
// `delivered` has been set to true by deliverViaDirect.
if (delivered) {
return {
delivered,
deliveryAttempted,
summary,
outputText,
synthesizedText,
deliveryPayloads,
};
}
}
if (announceResult) {
return {
result: announceResult,
delivered,