Skip to content

Commit 2c37968

Browse files
committed
fix(outbound): track best-effort failures when onError is absent (#29148)
1 parent 8db9185 commit 2c37968

File tree

2 files changed

+36
-9
lines changed

2 files changed

+36
-9
lines changed

src/infra/outbound/deliver.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,31 @@ describe("deliverOutboundPayloads", () => {
667667
);
668668
});
669669

670+
it("calls failDelivery on bestEffort partial failure when onError is absent", async () => {
671+
const sendWhatsApp = vi
672+
.fn()
673+
.mockRejectedValueOnce(new Error("fail"))
674+
.mockResolvedValueOnce({ messageId: "w2", toJid: "jid" });
675+
const cfg: OpenClawConfig = {};
676+
677+
const results = await deliverOutboundPayloads({
678+
cfg,
679+
channel: "whatsapp",
680+
to: "+1555",
681+
payloads: [{ text: "a" }, { text: "b" }],
682+
deps: { sendWhatsApp },
683+
bestEffort: true,
684+
});
685+
686+
expect(sendWhatsApp).toHaveBeenCalledTimes(2);
687+
expect(results).toEqual([{ channel: "whatsapp", messageId: "w2", toJid: "jid" }]);
688+
expect(queueMocks.ackDelivery).not.toHaveBeenCalled();
689+
expect(queueMocks.failDelivery).toHaveBeenCalledWith(
690+
"mock-queue-id",
691+
"partial delivery failure (bestEffort)",
692+
);
693+
});
694+
670695
it("acks the queue entry when delivery is aborted", async () => {
671696
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
672697
const abortController = new AbortController();

src/infra/outbound/deliver.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,18 @@ export async function deliverOutboundPayloads(
264264
// When bestEffort is true, per-payload errors are caught and passed to onError
265265
// without throwing — so the outer try/catch never fires. We track whether any
266266
// payload failed so we can call failDelivery instead of ackDelivery.
267+
// When bestEffort is true but onError is absent, we still wrap so hadPartialFailure is set.
267268
let hadPartialFailure = false;
268-
const wrappedParams = params.onError
269-
? {
270-
...params,
271-
onError: (err: unknown, payload: NormalizedOutboundPayload) => {
272-
hadPartialFailure = true;
273-
params.onError!(err, payload);
274-
},
275-
}
276-
: params;
269+
const wrappedParams =
270+
params.bestEffort || params.onError
271+
? {
272+
...params,
273+
onError: (err: unknown, payload: NormalizedOutboundPayload) => {
274+
hadPartialFailure = true;
275+
params.onError?.(err, payload);
276+
},
277+
}
278+
: params;
277279

278280
try {
279281
const results = await deliverOutboundPayloadsCore(wrappedParams);

0 commit comments

Comments
 (0)