Describe the bug
packages/dofs ships two runners over the same src/**/*.test.ts sources:
npm test → vitest run, the node:sqlite project (src/fs/with-db.ts)
npm run test:workers → vitest run --config vitest.config.workers.ts, which aliases every with-db.js import to src/fs/with-db.workers.ts so withDB drives a real Durable Object's SqlStorage
CI runs only the first (.github/workflows/ci.yml:101, npm test --workspace). The second is never invoked by any workflow. Since every consumer of dofs runs inside a Durable Object, the runtime the package actually ships on has no CI coverage at all — only node:sqlite, which src/testing.ts:1-4 itself describes as a superset ("Workers' DO SQL surface is a subset of this"). Anywhere the real DO diverges — RETURNING, json_each, the recursive CTE in fs/resolve.ts:137-160, transactionSync rollback semantics — is currently unverified.
The suite is also red, which is presumably why it was never wired up. On a clean checkout of 76d9e75:
$ npm run test:workers --workspace @cloudflare/dofs
RUN v4.1.10 packages/dofs
*** Received signal #11: Segmentation fault: 11
*** Received signal #11: Segmentation fault: 11
⎯⎯⎯ Unhandled Errors ⎯⎯⎯
Vitest caught 2 unhandled errors during the test run.
Error: [vitest-pool]: Worker cloudflare-pool emitted error.
Caused by: Error: Worker exited unexpectedly
Test Files 34 passed (36)
Tests 412 passed (412)
Errors 2 errors
npm error code 1
Exit status 1, two files never report, 24 tests silently absent.
Bisecting one file at a time identifies both crashers:
SEGFAULT: src/fs/filesystem.test.ts
SEGFAULT: src/schema/index.test.ts
Root cause is the same for both: each imports SQLiteTestStorage, which pulls node:sqlite in at module scope (src/testing.ts:14). That module's own header already states the constraint (src/testing.ts:6-7):
This module imports node:sqlite at the top level and therefore cannot be loaded under workerd.
Under workerd this is not a catchable module-resolution error — it takes the pool worker down with SIGSEGV, so the failure surfaces as an unhandled pool error rather than a test failure. Vitest still prints 34 passed and a green-looking test count above the error block, which makes the run easy to misread as "passing with some noise."
vitest.config.workers.ts:33 excludes only one of the three files that import the fixture:
exclude: ["src/testing.test.ts"],
and the comment above it (:27-32) asserts something that is not true of the other two:
All other tests run under both backends
src/fs/filesystem.test.ts:14 and src/schema/index.test.ts:1 both import it and are both included by include: ["src/**/*.test.ts"].
Expected behavior
npm run test:workers --workspace @cloudflare/dofs exits 0 with every included file reporting, and CI runs it on every PR, so a change that works against node:sqlite but breaks on real Durable Object SQLite fails the build.
Steps to reproduce
From a clean checkout at 76d9e75:
npm ci
npm run build --workspace @cloudflare/dofs
npm test --workspace @cloudflare/dofs → 37 files, 436 tests, exit 0 (this is all CI ever runs)
npm run test:workers --workspace @cloudflare/dofs → 2 segfaults, 34/36 files, exit 1
- Isolate either crasher directly, e.g.
npx vitest run --config vitest.config.workers.ts src/fs/filesystem.test.ts
grep -rln SQLiteTestStorage packages/dofs/src --include='*.test.ts' → three files; only one is excluded
Proposed fix
The two crashers need different treatment, because only one of them is legitimately node-only.
src/fs/filesystem.test.ts — make it portable, don't exclude it. It is a smoke test of the WorkspaceFilesystem wrapper and reaches for SQLiteTestStorage only to obtain a (db, now) pair. Routing it through withDB — which the workers config already aliases — makes it run under both backends:
async function withFs<T>(
fn: (fs: WorkspaceFilesystem) => T | Promise<T>,
now: () => number = () => 1234,
): Promise<T> {
return withDB((db) => fn(new WorkspaceFilesystem(db, { now })), { now });
}
This is a net gain of 13 tests against a real DO, not a mute.
src/schema/index.test.ts — exclude it, with an accurate comment. It drives the v1 → v5 migrations, which need a raw handle to a pre-migration database (:85, :134, :208, :265 each construct one from raw SQL). withDB always returns a DB already at SCHEMA_VERSION, so those fixtures cannot be built through it. Exclusion is correct here; the surrounding comment should say why, and should note that the failure mode for a stray node:sqlite import is a segfault rather than an import error.
Wire the project into CI. Add an optional per-package extra test step to the package matrix job, set on the dofs entry:
- name: dofs
workspace: "@cloudflare/dofs"
path: packages/dofs
needs-siblings: false
extra-test: "test:workers"
- name: Test (${{ matrix.extra-test }})
if: matrix.extra-test
run: npm run ${{ matrix.extra-test }} --workspace ${{ matrix.workspace }}
The if: guard keeps every other matrix entry unchanged, and the same seam is there for packages/computerd later if wanted.
Results with the three changes applied:
|
before |
after |
test:workers exit code |
1 |
0 |
| segfaults |
2 |
0 |
| files / tests |
34/36, 412 |
35/35, 425 |
| added CI time |
— |
~5.8s |
Verification:
$ npx vitest run --config vitest.config.workers.ts
Test Files 35 passed (35)
Tests 425 passed (425)
Duration 5.78s
$ npx vitest run # node project, unchanged
Test Files 37 passed (37)
Tests 436 passed (436)
$ npx biome check packages/dofs
Checked 96 files. No fixes applied.
Total diff is 33 insertions / 17 deletions across .github/workflows/ci.yml, packages/dofs/src/fs/filesystem.test.ts, and packages/dofs/vitest.config.workers.ts. No src/ behavior change — test and CI wiring only, so there is nothing to migrate and no compatibility surface. Happy to open the PR if you want it.
Environment
cloudflare/computer at 76d9e75 (current main), local checkout
- Node v25.2.1, npm 11.6.2, macOS 15.7.3 arm64
vitest 4.1.10, @cloudflare/vitest-pool-workers ^0.16.10
- Baselines on this machine:
npm test --workspace @cloudflare/dofs 436/436 pass; npm run test:workers exits 1
- The
SIGSEGV signal is what workerd does with a top-level node:sqlite import on this platform; the structural facts are platform-independent — test:workers exits non-zero, CI never invokes it, and two included files import a module documented as unloadable under workerd
Describe the bug
packages/dofsships two runners over the samesrc/**/*.test.tssources:npm test→vitest run, the node:sqlite project (src/fs/with-db.ts)npm run test:workers→vitest run --config vitest.config.workers.ts, which aliases everywith-db.jsimport tosrc/fs/with-db.workers.tssowithDBdrives a real Durable Object'sSqlStorageCI runs only the first (
.github/workflows/ci.yml:101,npm test --workspace). The second is never invoked by any workflow. Since every consumer of dofs runs inside a Durable Object, the runtime the package actually ships on has no CI coverage at all — only node:sqlite, whichsrc/testing.ts:1-4itself describes as a superset ("Workers' DO SQL surface is a subset of this"). Anywhere the real DO diverges —RETURNING,json_each, the recursive CTE infs/resolve.ts:137-160,transactionSyncrollback semantics — is currently unverified.The suite is also red, which is presumably why it was never wired up. On a clean checkout of
76d9e75:Exit status 1, two files never report, 24 tests silently absent.
Bisecting one file at a time identifies both crashers:
Root cause is the same for both: each imports
SQLiteTestStorage, which pullsnode:sqlitein at module scope (src/testing.ts:14). That module's own header already states the constraint (src/testing.ts:6-7):Under workerd this is not a catchable module-resolution error — it takes the pool worker down with
SIGSEGV, so the failure surfaces as an unhandled pool error rather than a test failure. Vitest still prints34 passedand a green-looking test count above the error block, which makes the run easy to misread as "passing with some noise."vitest.config.workers.ts:33excludes only one of the three files that import the fixture:and the comment above it (
:27-32) asserts something that is not true of the other two:src/fs/filesystem.test.ts:14andsrc/schema/index.test.ts:1both import it and are both included byinclude: ["src/**/*.test.ts"].Expected behavior
npm run test:workers --workspace @cloudflare/dofsexits 0 with every included file reporting, and CI runs it on every PR, so a change that works against node:sqlite but breaks on real Durable Object SQLite fails the build.Steps to reproduce
From a clean checkout at
76d9e75:npm cinpm run build --workspace @cloudflare/dofsnpm test --workspace @cloudflare/dofs→ 37 files, 436 tests, exit 0 (this is all CI ever runs)npm run test:workers --workspace @cloudflare/dofs→ 2 segfaults, 34/36 files, exit 1npx vitest run --config vitest.config.workers.ts src/fs/filesystem.test.tsgrep -rln SQLiteTestStorage packages/dofs/src --include='*.test.ts'→ three files; only one is excludedProposed fix
The two crashers need different treatment, because only one of them is legitimately node-only.
src/fs/filesystem.test.ts— make it portable, don't exclude it. It is a smoke test of theWorkspaceFilesystemwrapper and reaches forSQLiteTestStorageonly to obtain a(db, now)pair. Routing it throughwithDB— which the workers config already aliases — makes it run under both backends:This is a net gain of 13 tests against a real DO, not a mute.
src/schema/index.test.ts— exclude it, with an accurate comment. It drives the v1 → v5 migrations, which need a raw handle to a pre-migration database (:85,:134,:208,:265each construct one from raw SQL).withDBalways returns a DB already atSCHEMA_VERSION, so those fixtures cannot be built through it. Exclusion is correct here; the surrounding comment should say why, and should note that the failure mode for a straynode:sqliteimport is a segfault rather than an import error.Wire the project into CI. Add an optional per-package extra test step to the
packagematrix job, set on the dofs entry:The
if:guard keeps every other matrix entry unchanged, and the same seam is there forpackages/computerdlater if wanted.Results with the three changes applied:
test:workersexit codeVerification:
Total diff is 33 insertions / 17 deletions across
.github/workflows/ci.yml,packages/dofs/src/fs/filesystem.test.ts, andpackages/dofs/vitest.config.workers.ts. Nosrc/behavior change — test and CI wiring only, so there is nothing to migrate and no compatibility surface. Happy to open the PR if you want it.Environment
cloudflare/computerat76d9e75(currentmain), local checkoutvitest4.1.10,@cloudflare/vitest-pool-workers^0.16.10npm test --workspace @cloudflare/dofs436/436 pass;npm run test:workersexits 1SIGSEGVsignal is what workerd does with a top-levelnode:sqliteimport on this platform; the structural facts are platform-independent —test:workersexits non-zero, CI never invokes it, and two included files import a module documented as unloadable under workerd