cloudflare / cloudflare/workers-sdk
wrangler dev --local crashes the whole process on a recoverable D1 SQLITE_BUSY under concurrent writes (miniflare 4.20260722; regression from 4.20260714)
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 186
Description
### Which Cloudflare product(s) does this pertain to?
Wrangler core, Miniflare (local D1)
### Wrangler / Miniflare version
- **Affected:** `wrangler@4.114.0` (miniflare `4.20260722.0`, workerd `1.20260722.1`) and `wrangler@4.115.0` (miniflare `4.20260722.1`)
- **Not affected:** `wrangler@4.112.0` (miniflare `4.20260714.0`, workerd `1.20260714.1`)
### Description
Under `wrangler dev --local`, a **recoverable local D1 `SQLITE_BUSY` (`database is locked`) crashes the entire `wrangler dev` process** instead of surfacing to the Worker as a retryable error.
The D1 `queryExecute` failure is raised as a workerd `jsgInternalError`, propagates out of miniflare's loopback custom-fetch handler, and `ProxyController.emitErrorEvent` turns it into an uncaught error that **exits the dev server**. Because a single `wrangler dev` invocation hosts several service-bound workers (`-c` multi-config), the crash takes **all** workers down at once — every subsequent request gets `ECONNREFUSED`.
This is a **regression**: on `wrangler@4.112.0` (miniflare `4.20260714.0`) the same `SQLITE_BUSY` is retried and the dev server stays up (you can see the internal "Retrying D1 … due to transient error" path fire). It first breaks at `4.114.0` (miniflare `4.20260722.0`) and still reproduces on `4.115.0`.
### Steps to reproduce
Trigger a **local D1 `SQLITE_BUSY`** by issuing concurrent writes against the same local D1 database — e.g. several parallel requests that each `INSERT`/`UPDATE` (our repro is an E2E suite running 4 parallel browser workers that concurrently create orgs + users through a Worker backed by local D1).
1. `wrangler dev --local --persist-to ` with one or more service-bound workers that write to a shared local D1.
2. Drive enough concurrent write traffic that miniflare's SQLite returns `SQLITE_BUSY` / `SQLITE_BUSY_SNAPSHOT`.
3. On `wrangler@4.114.0`/`4.115.0` the dev process exits at the first such error; on `4.112.0` it retries and stays up.
(Happy to build a minimal standalone repro if the signature below isn't enough to locate it.)
### Relevant log / crash signature
```
✘ [ERROR] e = workerd/util/sqlite.c++:1671: failed: SENTRY_DO SQLite failed;
NOSENTRY database is locked: SQLITE_BUSY (extended: SQLITE_BUSY_SNAPSHOT)
sentryErrorContext = jsgInternalError; wdErrId =
[wrangler:error] Error: internal error; reference =
at D1DatabaseObject.queryExecute (miniflare/src/workers/d1/database.worker.ts:228:31)
at async D1DatabaseObject.fetch (miniflare/src/workers/shared/router.worker.ts:45:32)
Error in ProxyController: Error inside ProxyWorker
at castErrorCause (wrangler-dist/cli.js)
at ProxyController2.emitErrorEvent (wrangler-dist/cli.js)
at ProxyController2.onProxyWorkerMessage (wrangler-dist/cli.js)
at #handleLoopbackCustomFetchService (miniflare/dist/src/index.js)
at #handleLoopback (miniflare/dist/src/index.js)
"event":"wrangler command errored","wranglerVersion":"4.114.0","sanitizedCommand":"dev"
# -> wrangler dev process exits; all workers now ECONNREFUSED
```
### Expected behaviour
A local D1 `SQLITE_BUSY` should be retried internally and/or surfaced to the Worker as a normal query error — it must **not** crash the `wrangler dev` process. (This is how `4.112.0` / miniflare `4.20260714.0` behaves.)
### Actual behaviour
The error escapes miniflare's loopback handler, `ProxyController.emitErrorEvent` rethrows, and the whole `wrangler dev` process exits, taking every hosted worker offline.
### Environment
- OS: Linux (Ubuntu), Node 26, package manager: bun
- `wrangler dev --local --persist-to ` with multiple `-c` service configs (router + service-bound workers), D1 local backing store
- Intermittent — depends on a `SQLITE_BUSY` landing under concurrent writes
### Bisect
Regressed between `wrangler@4.112.0` (miniflare `4.20260714.0`) — good — and `wrangler@4.114.0` (miniflare `4.20260722.0`) — bad. `4.115.0` (miniflare `4.20260722.1`) still affected. Current workaround: pin `wrangler@4.112.0`.
Contributor guide
Research direction
Start in miniflare/src/workers/d1/database.worker.ts and compare the D1 queryExecute error path with the working 4.112.0 behavior. Trace how the error reaches miniflare/src/workers/shared/router.worker.ts and the loopback ProxyController handler. Done means concurrent local D1 SQLITE_BUSY errors are retried or returned as query errors without exiting wrangler dev or taking other workers offline.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- sqlite, typescript
- Domain
- cli, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100