# OpenSpec Change: testing-integration-bun-runtime-compat - **Change ID**: testing-integration-bun-runtime-compat - **Package**: `@suaveplan/testing-integration` (modify) + every `testing-integration-*` package - **Status**: PROPOSED - **Date**: 2026-04-28 ## Summary Make `bunx vitest run` actually start testcontainers in this monorepo. Today every `testing-integration-*` package lands in env-bridge mode only; the moment a fixture tries to spin a container it crashes or hangs. Two distinct Bun limitations are stacked on top of each other: 1. **Default wait strategy hangs** — testcontainers' default `Wait.forListeningPorts()` runs port probes inside Bun's net stack and never resolves on macOS/Linux. Bun issue [oven-sh/bun#21342](https://github.com/oven-sh/bun/issues/21342) (open since 2025-07-24, reproed on Bun 1.3.2 in March 2026) tracks this; community workaround is `Wait.forLogMessage(...)` + explicit port exposure. 2. **`uv_version_string` panic** — `dockerode` (which testcontainers uses for the Docker socket) pulls `ssh2`, whose NAPI sshcrypto.node calls `uv_version_string` at module-load. Bun's libuv shim doesn't implement it ([oven-sh/bun#18546](https://github.com/oven-sh/bun/issues/18546)). Crashes the runner before any fixture runs. Today's symptom in this repo: `bun run --filter=@suaveplan/storage-s3 test:integration` panics on the second issue before the first is even reached. ## Current state - Repo standard runner: `bunx vitest` via Turbo. CLAUDE.md mandates Bun for everything. - All existing `testing-integration-*` unit tests inject fakes (`containerFactory`, `execFn`, `fetchFn`); no test in the monorepo has ever exercised a real container under Bun. - `@suaveplan/storage-s3`'s `set-partial.integration.test.ts` is the first real consumer; on Bun it skips with no useful diagnostic. ## What changes Three options, with cost increasing: ### Option A (cheapest): Document + dual-runner switch - Add a project-wide `test:integration` Turbo pipeline that invokes `node $(bun pm ls -g vitest | grep ...)` (or shells `npx vitest run`) instead of `bunx vitest`. Keeps `test` (unit) on Bun, sends `test:integration` to Node where testcontainers works today. - Add a clear log line + early-skip when `process.versions.bun !== undefined` and the fixture would start a container. - Update CLAUDE.md to note the runtime split for `test:integration`. Time-to-green: hours. Tradeoff: Bun-purity violation for one task. ### Option B (medium): Bun-friendly fixture defaults + bun-test-compatible wiring Per Bun #21342 the wait-strategy hang is the load-bearing issue, not `uv_version_string`. The `ssh2` panic only fires because `dockerode` is in the dependency graph; testcontainers v11+ uses `dockerode-modem`-light paths for most operations, and **modeshift** ports of testcontainers exist that drop ssh2 entirely. Plan: - Pin every `*-fixture.ts` to `Wait.forLogMessage(, { times: })` rather than the default `forListeningPorts`. Per #21342 thumbs-up workaround this resolves the hang on Bun 1.3.0+. - Audit testcontainers deps for `ssh2`. If it's still pulled in transitively, replace `testcontainers` with a minimal in-repo `docker run -d` shell + `docker exec` polling wrapper (per `eyueldk`'s working pattern, comment 2025-11-23). The seven-step Garage init sequence already shells out, so this is mostly pre-paved. - Migrate fixture self-tests to `bun:test` via the existing `@suaveplan/testing/runner` dual-runner abstraction. Time-to-green: ~1 sprint. Tradeoff: forking testcontainers behaviour; carry-cost. ### Option C: Wait for upstream Track Bun #21342. Currently the team has not assigned a milestone (open + unprioritised at 9 months old). Not a viable plan — would block real container coverage indefinitely. ## Recommendation **Ship Option A immediately** — unblocks `@suaveplan/storage-s3` and any other package wanting real-container coverage today, with a one-runner-deviation cost that's clearly documented. **Plan Option B as a follow-up** once we have ≥3 `*-integration` packages actively consuming containers. By then the Bun upstream picture may also have moved. ## Impact - Option A: Adds a divergent runner for `test:integration` only. Default `test` stays on Bun. Roughly +20s startup per integration run (Node spin-up + module resolution). - Unblocks every existing `testing-integration-*` package for real-container coverage today. - `@suaveplan/storage-s3` integration test will run automatically once Option A lands; no test changes needed. ## Out of scope - The `createIntegrationSetup` data-URL emission bug (separate change: `testing-integration-build-fix`). - Adding new fixtures. - Migrating to a different test runner for the unit-test path.