diff --git a/src/runs/postgres-run-store.ts b/src/runs/postgres-run-store.ts index 4ab32edc..ae12c416 100644 --- a/src/runs/postgres-run-store.ts +++ b/src/runs/postgres-run-store.ts @@ -290,6 +290,7 @@ export function createPostgresRunStore(connectionString: string, opts?: { maxCla if (done) return; done = true; clearInterval(poll); + clearTimeout(timer); events.off(runId, onSettle); resolve(r); }; @@ -298,9 +299,13 @@ export function createPostgresRunStore(connectionString: string, opts?: { maxCla } events.once(runId, onSettle); const poll = setInterval(() => { - void getRun(runId).then((r) => { - if (r && isTerminal(r.status)) finish(r); - }); + void getRun(runId) + .then((r) => { + if (r && isTerminal(r.status)) finish(r); + }) + .catch((err: unknown) => { + console.error(`[postgres-run-store] waitFor poll for run ${runId} failed transiently:`, err); + }); }, 250); poll.unref?.(); const timer = setTimeout(() => { diff --git a/test/postgres-store.test.ts b/test/postgres-store.test.ts index d9e0b091..c591cc9f 100644 --- a/test/postgres-store.test.ts +++ b/test/postgres-store.test.ts @@ -893,6 +893,31 @@ test("pg run store: enqueue dedup, atomic one-per-session claim, fencing, ledger } }); +test( + "pg run store: waitFor survives a transient poll failure without an unhandled rejection", + { skip }, + async () => { + const { runs, close } = createPostgresRunStore(URL!); + let unhandled: unknown; + const onUnhandledRejection = (err: unknown): void => { + unhandled = err; + }; + process.once("unhandledRejection", onUnhandledRejection); + try { + const r = (await runs.enqueue({ sessionId: "sWaitFor", request: turn("x") })).run; + const pending = runs.waitFor(r.id, 800); + // Kill the pool mid-poll: the next getRun() tick will reject, exercising the + // catch path instead of leaking an unhandled rejection out of setInterval. + await new Promise((res) => setTimeout(res, 50)); + await close(); + await assert.rejects(pending, /did not finish within 800ms/, "still settles via its own timeout, not a crash"); + } finally { + process.removeListener("unhandledRejection", onUnhandledRejection); + } + assert.equal(unhandled, undefined, "a transient poll failure must not escape as an unhandled rejection"); + }, +); + test( "pg run store: releaseLease (deploy drain) hands the run back as a retry without spending budget; stale token is a no-op", { skip },