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)

Open
#14,916 2 comments 0 reactions 0 assignees View on GitHub
package:miniflare product:d1 regression
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.