Skip to content

Commit 5892ebf

Browse files
NERLOEclaude
andcommitted
fix(core): accept IPC acks for void callbacks under zod 4.4+
FLUSH and CANCEL declare `callback: z.void()`, so the child acks with `message: undefined`. Node's JSON IPC serialization drops that key, and zod 4.4+ treats a `z.any()` key as required, so the ACK packet failed to parse and `sendWithAck` waited out its full timeout: ~6s on every run completion and ~31s on cancel. Make the ACK packet's `message` optional. Fixes #4979 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent c2b7a72 commit 5892ebf

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

‎.changeset/void-ipc-acks.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Fix a ~6 second delay between a task finishing and its run completing (and a ~31 second delay when cancelling a run) in projects that use zod 4.4 or newer.

‎packages/core/src/v3/zodIpc.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ const Packet = discriminatedUnion("type", [
124124
}),
125125
z.object({
126126
type: z.literal("ACK"),
127-
message: z.any(),
127+
// Optional: a void callback acks with `message: undefined`, and `process.send`
128+
// drops undefined keys when serializing, so the key is absent on arrival.
129+
message: z.any().optional(),
128130
id: z.number(),
129131
}),
130132
z.object({

‎packages/core/test/zodIpc.test.ts‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { EventEmitter } from "node:events";
2+
import { describe, expect, it } from "vitest";
3+
import { z } from "zod/v4";
4+
import { ZodIpcConnection } from "../src/v3/zodIpc.js";
5+
6+
// A pair of in-memory endpoints that serialize packets the same way Node's default
7+
// ("json") IPC serialization does, so keys with `undefined` values are dropped in transit.
8+
function createIpcPair() {
9+
const parentEvents = new EventEmitter();
10+
const childEvents = new EventEmitter();
11+
12+
const endpoint = (inbox: EventEmitter, outbox: EventEmitter) => ({
13+
connected: true,
14+
send: (message: unknown) => {
15+
const serialized = JSON.stringify(message);
16+
setImmediate(() => outbox.emit("message", JSON.parse(serialized)));
17+
return true;
18+
},
19+
on: (event: "message", listener: (message: any) => void) => {
20+
inbox.on(event, listener);
21+
},
22+
});
23+
24+
return {
25+
parent: endpoint(parentEvents, childEvents),
26+
child: endpoint(childEvents, parentEvents),
27+
};
28+
}
29+
30+
const ParentToChild = {
31+
FLUSH: {
32+
message: z.object({ timeoutInMs: z.number() }),
33+
callback: z.void(),
34+
},
35+
PING: {
36+
message: z.object({ value: z.string() }),
37+
callback: z.object({ echoed: z.string() }),
38+
},
39+
};
40+
41+
const ChildToParent = {};
42+
43+
function createConnections() {
44+
const { parent, child } = createIpcPair();
45+
46+
const parentConnection = new ZodIpcConnection({
47+
listenSchema: ChildToParent,
48+
emitSchema: ParentToChild,
49+
process: parent,
50+
});
51+
52+
new ZodIpcConnection({
53+
listenSchema: ParentToChild,
54+
emitSchema: ChildToParent,
55+
process: child,
56+
handlers: {
57+
FLUSH: async () => {},
58+
PING: async ({ value }) => ({ echoed: value }),
59+
},
60+
});
61+
62+
return parentConnection;
63+
}
64+
65+
describe("ZodIpcConnection", () => {
66+
it("resolves sendWithAck for a void callback after the ack crosses a JSON boundary", async () => {
67+
const connection = createConnections();
68+
69+
const start = Date.now();
70+
await expect(connection.sendWithAck("FLUSH", { timeoutInMs: 1000 }, 1000)).resolves.toBe(
71+
undefined
72+
);
73+
expect(Date.now() - start).toBeLessThan(500);
74+
});
75+
76+
it("resolves sendWithAck with the callback payload", async () => {
77+
const connection = createConnections();
78+
79+
await expect(connection.sendWithAck("PING", { value: "hello" }, 1000)).resolves.toEqual({
80+
echoed: "hello",
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)