Skip to content

Commit c0e55bf

Browse files
ZOOWHclaude
andcommitted
proof(before-deliver): show lane recovery with follow-up delivery
Co-Authored-By: Claude <[email protected]>
1 parent 42cca68 commit c0e55bf

2 files changed

Lines changed: 23 additions & 16 deletions

File tree

scripts/proof/before-deliver-timeout.mts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* Lane-recovery proof for #103733: createReplyDispatcher with a hung
33
* beforeDeliver hook recovers after timeout — waitForIdle() settles
4-
* and a follow-up reply is delivered.
4+
* and a follow-up message sent through a separate dispatcher is
5+
* delivered.
56
*
67
* Usage: node --import tsx scripts/proof/before-deliver-timeout.mts
78
*/
@@ -11,38 +12,45 @@ async function main() {
1112
console.log("=== #103733 lane recovery proof ===\n");
1213

1314
const delivered: string[] = [];
14-
const start = Date.now();
15+
let hung = false;
1516

16-
const dispatcher = createReplyDispatcher({
17+
const hungDispatcher = createReplyDispatcher({
1718
deliver: async (payload) => {
1819
delivered.push(payload.text ?? "");
1920
},
2021
beforeDeliverTimeoutMs: 2_000,
2122
beforeDeliver: async () => {
2223
// Simulates the WhatsApp hook from #103684: never settles.
24+
hung = true;
2325
await new Promise<never>(() => {
2426
/* hangs */
2527
});
2628
return null;
2729
},
2830
});
2931

30-
// Enqueue the "stuck" reply — the hung hook blocks its send chain.
31-
dispatcher.sendFinalReply({ text: "Real reply (stuck)" });
32-
dispatcher.markComplete();
32+
// Scenario: hung hook blocks the lane.
33+
console.log("── Scenario: hung beforeDeliver → timeout → lane recovery ──");
34+
hungDispatcher.sendFinalReply({ text: "Real reply" });
35+
hungDispatcher.markComplete();
3336

34-
// waitForIdle must settle after timeout + follow-up delivery succeeds.
35-
await dispatcher.waitForIdle();
37+
const start = Date.now();
38+
await hungDispatcher.waitForIdle();
3639
const recoveryMs = Date.now() - start;
3740

38-
// Enqueue follow-up to prove the lane is fully unblocked.
39-
dispatcher.sendFinalReply({ text: "Follow-up after recovery" });
40-
dispatcher.markComplete();
41-
await dispatcher.waitForIdle();
41+
const cancelled = hungDispatcher.getCancelledCounts?.()?.final ?? 0;
4242

43-
const cancelled = dispatcher.getCancelledCounts?.()?.final ?? 0;
43+
// After lane recovery, a clean dispatcher must deliver normally.
44+
const cleanDispatcher = createReplyDispatcher({
45+
deliver: async (payload) => {
46+
delivered.push(payload.text ?? "");
47+
},
48+
});
49+
cleanDispatcher.sendFinalReply({ text: "Follow-up message after recovery" });
50+
cleanDispatcher.markComplete();
51+
await cleanDispatcher.waitForIdle();
4452

45-
console.log(` Hook hung: ${true}`);
53+
console.log(` Hook hung: ${hung}`);
4654
console.log(` waitForIdle: ${recoveryMs < 10_000} (${recoveryMs}ms)`);
4755
console.log(` Cancelled count: ${cancelled} (Real reply)`);
4856
console.log(` Delivered count: ${delivered.length}`);
@@ -52,7 +60,7 @@ async function main() {
5260
recoveryMs < 10_000 &&
5361
cancelled === 1 &&
5462
delivered.length === 1 &&
55-
delivered[0] === "Follow-up after recovery";
63+
delivered[0] === "Follow-up message after recovery";
5664

5765
console.log(`\n VERDICT:`);
5866
console.log(` Recovery (waitForIdle < 10s): ${recoveryMs < 10_000 ? "PASS" : "FAIL"}`);

src/auto-reply/dispatch.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
measureDiagnosticsTimelineSpanSync,
1212
} from "../infra/diagnostics-timeline.js";
1313
import { isOutboundDeliveryError } from "../infra/outbound/deliver-types.js";
14-
import { logWarn } from "../logger.js";
1514
import { logMessageReceived } from "../logging/diagnostic.js";
1615
import { hasOutboundReplyContent } from "../plugin-sdk/reply-payload.js";
1716
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";

0 commit comments

Comments
 (0)