Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

adapter-cloudflare 8: concurrent Vite servers each start a platform proxy, so Vitest projects collide on the same SQLite (SQLITE_BUSY_RECOVERY)

Closed Beginner friendly
#17,344 2 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@glw907 is already working on this.

Since Oct 6, 2026.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
70/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, sqlite, vite
Domain
tooling

Research direction

Start in the adapter-cloudflare plugin source defining virtual_workers_module(): setup() guards on globalThis.__sveltekit_cloudflare_platform but only assigns it after awaiting getPlatformProxy, and dispose() releases it. Cache the in-flight getPlatformProxy promise instead and clear it in dispose, as the suggested patch does. Reproduce with a two-project Vitest config (client/server) extending sveltekit({ adapter: adapter() }); run vitest --run repeatedly after a vite dev. Done looks like one 'Using secrets defined in .dev.vars' line, no SQLITE_BUSY, no exit warning. Context: #16623, #17238.

Written by the indexing model from the issue text.

Description

pkg:adapter-cloudflare
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
  1. A SvelteKit 3 project with @sveltejs/adapter-cloudflare 8.0.0 and a wrangler.jsonc with a D1 binding.

  2. 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' } },
    ] }
    
  3. 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)

Dominant language
JavaScript
Stars
20.9k
Forks
2.4k
Avg merge
1d 23h
Merged PRs (30d)
89

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from sveltejs/kit

All issues in sveltejs/kit

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.