From 3491ff5836f4dcdd4ac37dc7e0d50c487d2422e3 Mon Sep 17 00:00:00 2001 From: octo-patch Date: Tue, 21 Apr 2026 09:12:06 +0800 Subject: [PATCH 1/2] fix(serverless-hono): withWaitUntil must not destroy global state when context has no waitUntil Two bugs in withWaitUntil: 1. The else branch unconditionally set ___voltagent_wait_until = undefined even when the global already held a value from an outer scope. This silently killed background tasks (observability, logging) in middleware chains where an inner handler passed {} as context. 2. Errors thrown by context.waitUntil (e.g. DataCloneError in Cloudflare Workers) propagated to callers. The test suite expected them to be swallowed; wrapping the call in try/catch aligns implementation with that contract. Fix: - Remove the else branch entirely. When context has no waitUntil the global is left untouched. - Wrap the bound waitUntil call in try/catch to swallow errors. - Simplify the cleanup function to always restore previousWaitUntil unconditionally (no currentWaitUntil guard needed). All 11 tests in wait-until-wrapper.spec.ts now pass. Fixes #1203 --- .../src/utils/wait-until-wrapper.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/serverless-hono/src/utils/wait-until-wrapper.ts b/packages/serverless-hono/src/utils/wait-until-wrapper.ts index 4ef4dcb78..c555e629e 100644 --- a/packages/serverless-hono/src/utils/wait-until-wrapper.ts +++ b/packages/serverless-hono/src/utils/wait-until-wrapper.ts @@ -34,20 +34,21 @@ export function withWaitUntil(context?: WaitUntilContext | null): () => void { if (currentWaitUntil && typeof currentWaitUntil === "function") { // Bind to context to avoid "Illegal invocation" errors - // And allow errors (like DataCloneError) to propagate so caller can handle fallback - globals.___voltagent_wait_until = currentWaitUntil.bind(context); - } else { - globals.___voltagent_wait_until = undefined; + // Wrap in try/catch so errors (like DataCloneError) are swallowed and don't break the caller + const boundWaitUntil = currentWaitUntil.bind(context); + globals.___voltagent_wait_until = (promise: Promise) => { + try { + boundWaitUntil(promise); + } catch { + // Swallow errors to avoid breaking the caller + } + }; } + // No else branch — don't touch global when context has no waitUntil, + // to avoid destroying a previously set value from an outer scope - // Return cleanup function + // Return cleanup function that always restores the previous state return () => { - if (currentWaitUntil) { - if (previousWaitUntil) { - globals.___voltagent_wait_until = previousWaitUntil; - } else { - globals.___voltagent_wait_until = undefined; - } - } + globals.___voltagent_wait_until = previousWaitUntil; }; } From b91b5d5e4138b633618c5d06d9e70a0eab0cad64 Mon Sep 17 00:00:00 2001 From: Omer Aplak Date: Wed, 22 Apr 2026 03:05:15 +0300 Subject: [PATCH 2/2] chore: add changeset --- .changeset/rude-pianos-marry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rude-pianos-marry.md diff --git a/.changeset/rude-pianos-marry.md b/.changeset/rude-pianos-marry.md new file mode 100644 index 000000000..38d9aea5e --- /dev/null +++ b/.changeset/rude-pianos-marry.md @@ -0,0 +1,5 @@ +--- +"@voltagent/serverless-hono": patch +--- + +fix(serverless-hono): withWaitUntil must not destroy global state when context has no waitUntil