Describe the bug
virtual_workers_module()'s setup() guards on globalThis.__sveltekit_cloudflare_platform and only assigns it after await getPlatformProxy(options) has finished:
const setup = async () => {
if (globalThis.__sveltekit_cloudflare_platform) return;
const proxy = await getPlatformProxy(options);
globalThis.__sveltekit_cloudflare_platform = proxy;
...
};
When two Vite servers start in the same process, both pass the guard before either finishes, so each starts its own getPlatformProxy() (its own workerd) on the same .wrangler/state. This is the same shape as #16623 (which described the old emulate() path), but in the new Vite plugin. Vitest triggers it every time: each project that extends a config using sveltekit({ adapter: adapter() }) gets its own Vite server, so a two-project setup (say client and server) starts two proxies at once.
The result is intermittent. Most often one workerd is repairing a journal left by an earlier unclean shutdown (a killed vite dev, say) while the other opens the same SQLite files, and the run dies in about two seconds:
Using secrets defined in .dev.vars
Using secrets defined in .dev.vars
*** Fatal uncaught kj::Exception: workerd/util/sqlite.c++:890: failed: SQLite failed; dbErrorMessage(prepareResult, db) = database is locked: SQLITE_BUSY (extended: SQLITE_BUSY_RECOVERY)
⎯⎯⎯⎯⎯⎯⎯ Startup Error ⎯⎯⎯⎯⎯⎯⎯⎯
AggregateError: Failed to initialize projects. There were errors during projects setup.
MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start.
... at getPlatformProxy
When it doesn't fail, the run still ends with Tests closed successfully but something prevents the main process from exiting, even with the dispose fix from #17238.
Reproduction
-
A SvelteKit 3 project with @sveltejs/adapter-cloudflare 8.0.0 and a wrangler.jsonc with a D1 binding.
-
A vite.config.ts with sveltekit({ adapter: adapter() }) and two Vitest projects that extends it:
test: { projects: [
{ extends: './vite.config.ts', test: { name: 'client', /* browser */ } },
{ extends: './vite.config.ts', test: { name: 'server', environment: 'node' } },
] }
-
vitest --run repeatedly. The setup prints "Using secrets defined in .dev.vars" twice (or two proxies in whatever form your logs show).
In my project, 3 of 5 consecutive runs failed this way (about 2 s each); the other two passed in about 19 s with the exit warning above. Failures are most likely right after vite dev, wrangler dev or a build, which leave state behind.
Suggested fix
Store the pending promise rather than the finished proxy, so concurrent servers share one proxy. I patched my local copy of 8.0.0 this way:
const setup = async () => {
if (globalThis.__sveltekit_cloudflare_platform) return;
const pending = (globalThis.__sveltekit_cloudflare_platform_pending ??= getPlatformProxy(options));
const proxy = await pending;
if (globalThis.__sveltekit_cloudflare_platform) return;
globalThis.__sveltekit_cloudflare_platform = proxy;
globalThis.caches = proxy.caches;
};
(dispose would also need to clear the pending promise.) With it, 5 of 5 runs started a single proxy, none failed, each took about 7 s instead of 19 s, and the exit warning was gone. I haven't checked the preview server or a restarting dev server.
Workaround for now: leave the adapter out of the Vite config when process.env.VITEST is set. Unit tests that don't need bindings don't need the proxy at all.
System Info
@sveltejs/kit 3.0.0, @sveltejs/adapter-cloudflare 8.0.0, wrangler 4.147.0
vite 8.3.2, vitest 4.1.11, @vitest/browser-playwright 4.1.11
node 26.10.0, bun 1.4.2, macOS 27.0.1
Severity
annoyance (intermittent test-start failure with a workaround)
Describe the bug
virtual_workers_module()'ssetup()guards onglobalThis.__sveltekit_cloudflare_platformand only assigns it afterawait getPlatformProxy(options)has finished:When two Vite servers start in the same process, both pass the guard before either finishes, so each starts its own
getPlatformProxy()(its own workerd) on the same.wrangler/state. This is the same shape as #16623 (which described the oldemulate()path), but in the new Vite plugin. Vitest triggers it every time: each project that extends a config usingsveltekit({ adapter: adapter() })gets its own Vite server, so a two-project setup (sayclientandserver) starts two proxies at once.The result is intermittent. Most often one workerd is repairing a journal left by an earlier unclean shutdown (a killed
vite dev, say) while the other opens the same SQLite files, and the run dies in about two seconds:When it doesn't fail, the run still ends with
Tests closed successfully but something prevents the main process from exiting, even with the dispose fix from #17238.Reproduction
A SvelteKit 3 project with
@sveltejs/adapter-cloudflare8.0.0 and awrangler.jsoncwith a D1 binding.A
vite.config.tswithsveltekit({ adapter: adapter() })and two Vitest projects thatextendsit:vitest --runrepeatedly. The setup prints "Using secrets defined in .dev.vars" twice (or two proxies in whatever form your logs show).In my project, 3 of 5 consecutive runs failed this way (about 2 s each); the other two passed in about 19 s with the exit warning above. Failures are most likely right after
vite dev,wrangler devor a build, which leave state behind.Suggested fix
Store the pending promise rather than the finished proxy, so concurrent servers share one proxy. I patched my local copy of 8.0.0 this way:
(
disposewould also need to clear the pending promise.) With it, 5 of 5 runs started a single proxy, none failed, each took about 7 s instead of 19 s, and the exit warning was gone. I haven't checked the preview server or a restarting dev server.Workaround for now: leave the adapter out of the Vite config when
process.env.VITESTis set. Unit tests that don't need bindings don't need the proxy at all.System Info
Severity
annoyance (intermittent test-start failure with a workaround)