diff --git a/src/cron/isolated-agent/delivery-dispatch-policy.ts b/src/cron/isolated-agent/delivery-dispatch-policy.ts index a017667435c6..46a92c55ccdf 100644 --- a/src/cron/isolated-agent/delivery-dispatch-policy.ts +++ b/src/cron/isolated-agent/delivery-dispatch-policy.ts @@ -15,7 +15,7 @@ import { loadDeliveryQueueEntry, type DeliveryQueueCompletionRetention, } from "../../infra/delivery-queue-sqlite.js"; -import { isProvenDeliveryNotSentError } from "../../infra/delivery-recovery.shared.js"; +import * as deliveryRecovery from "../../infra/delivery-recovery.shared.js"; import { isFastTestRuntimeEnv } from "../../infra/env.js"; import { OUTBOUND_DELIVERY_QUEUE_NAME } from "../../infra/outbound/delivery-queue-media-staging.js"; import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js"; @@ -254,6 +254,9 @@ function summarizeDirectCronDeliveryError(error: unknown): string { } function isTransientDirectCronDeliveryError(error: unknown): boolean { + if (deliveryRecovery.findPlatformMessageRejectedError(error)) { + return false; + } const message = summarizeDirectCronDeliveryError(error); if (!message) { return false; @@ -261,7 +264,7 @@ function isTransientDirectCronDeliveryError(error: unknown): boolean { if (PERMANENT_DIRECT_CRON_DELIVERY_ERROR_PATTERNS.some((re) => re.test(message))) { return false; } - return isProvenDeliveryNotSentError(error); + return deliveryRecovery.isProvenDeliveryNotSentError(error); } function resolveDirectCronRetryDelaysMs(): readonly number[] { return isFastTestRuntimeEnv() ? [0, 0, 0] : [5_000, 10_000, 20_000]; diff --git a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts index 7d60b2f161f1..89c7f8dcdba3 100644 --- a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts +++ b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts @@ -2327,6 +2327,25 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(deliverOutboundPayloads).toHaveBeenCalledTimes(2); }); + it("does not retry permanent typed pre-dispatch rejections", async () => { + vi.stubEnv("OPENCLAW_TEST_FAST", "1"); + const rejection = new PlatformMessageNotDispatchedError("payload rejected", { + cause: new Error("invalid payload"), + retryable: false, + }); + vi.mocked(deliverOutboundPayloads).mockRejectedValue(rejection); + + const params = makeBaseParams({ synthesizedText: "Reject this once." }); + const state = await dispatchCronDelivery(params); + + expect(deliverOutboundPayloads).toHaveBeenCalledTimes(1); + expectResultFields(state.result, { + status: "error", + error: String(rejection), + deliveryAttempted: true, + }); + }); + it.each(["structured", "threaded"] as const)( "retries proven-not-sent %s cron delivery without duplicating a message", async (deliveryKind) => {