Fix Deno 2.8+/2.9.2+ incompatibilities and unbreak CI - #108
Conversation
Deno 2.8.0 changed the return type of `setInterval` from `number` to `Timeout`, so the hard-coded `number` annotation fails type checking on Deno 2.8 and later. Since `deno test` type checks as well, this took down the whole test suite, not just `deno check`. Inferring from `setInterval` keeps the annotation correct across runtime versions, and matches what `subprocess.ts` already does for its timeout id. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1
Deno 2.9.2 regressed Web Streams so that a write to a TransformStream whose readable side was piped through another TransformStream never resolves until the final output is being consumed. The round-trip helper wrote first and attached its reader afterwards, so every serializer test deadlocked on Deno 2.9.2 and later. Reported upstream as denoland/deno#36790. Starting the read before the write sidesteps the deadlock and works on every Deno version. `encodeToCbor` in subprocess.ts already collects its output this way, so the production path was never affected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1
There was a problem hiding this comment.
🟡 Changes recommended
The new “read-before-write” test helper order introduces a failure-path risk of leaving an unawaited pending read promise (potential unhandled rejection) unless reader cancellation/cleanup is added.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the repo to work cleanly on newer Deno runtimes by fixing a timer typing incompatibility introduced in Deno 2.8+, addressing a Deno 2.9.2+ Web Streams deadlock in a CBOR round-trip test helper, and advancing the pinned nixpkgs revision to bring CI/tooling forward to a newer Deno.
Changes:
- Type the spinner interval ID using
ReturnType<typeof setInterval>to match Deno’s updated timer return type. - Reorder the CBOR streaming round-trip in
serializer_test.tsto begin reading before writing to avoid a Deno Web Streams deadlock. - Update the
nixpkgspin inflake.lockto a newer revision.
File summaries
| File | Description |
|---|---|
| src/cli/progress.ts | Adjusts spinner timer ID typing for Deno 2.8+ compatibility. |
| src/cli/_templates/serializer_test.ts | Changes stream read/write ordering to avoid deadlocks on newer Deno. |
| flake.lock | Updates pinned nixpkgs revision/hash to move the toolchain forward. |
Review details
- Files reviewed: 2/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The pinned nixpkgs shipped Deno 2.6.6, and every CI job runs through `nix develop`, so CI never exercised Deno 2.8 or 2.9. Both breakages fixed in the preceding commits were invisible here while being immediately visible to anyone with a current Deno. Moving the pin to Deno 2.9.5 makes CI run the version users actually have. The newer environment also breaks @db/sqlite: it downloads a prebuilt libsqlite3, and dlopening that against this glibc segfaults the process. The root module re-exports the SQLite client and @db/sqlite dlopens as it evaluates, so this killed every scenario subprocess, including scenarios that never touch SQLite. Pointing DENO_SQLITE_PATH at the nixpkgs library makes @db/sqlite skip the prebuilt entirely. It is set both in the devShell, which the test and scenario-test jobs use, and in the package wrapper that scenario-test-nix builds. Deno is not at fault here — 2.9.5 loads the same prebuilt library fine on macOS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1
LocalStack merged its community and pro images, and every image published after that merge quits with exit code 55 unless LOCALSTACK_AUTH_TOKEN is set. That took down test, scenario-test and scenario-test-nix at container init, before a single test ran, and compose.yaml hits the same wall locally. It went unnoticed because the last CI run on main predates the change. 4.14.0 is the last community release, so it starts without a token. This is a stopgap that freezes the SQS test environment at February 2026; #109 tracks replacing LocalStack with ElasticMQ, which needs no license at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1
…ncaught If the write into the encoder fails, the read that was started before it is abandoned and rejects with nobody awaiting it. Deno does not merely warn about that: it reports "This error was not caught from a test" and fails the whole test module, which under --parallel --shuffle looks like an unrelated test breaking. Marking the read handled keeps the write error the one that propagates, and still lets a genuine read rejection surface where it is awaited. Not reachable today, since toCborStreamInput always yields an encodable value, but it would bite the first test written for a value the encoder rejects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1
adc4fe7 to
a82038e
Compare
Summary
ReturnType<typeof setInterval>instead of a hard-codednumber, which Deno 2.8.0 broke when it changedsetIntervalto returnTimeout.serializer_test.ts, working around a Web Streams regression in Deno 2.9.2.DENO_SQLITE_PATHat the nixpkgs SQLite so@db/sqlitestops loading a prebuilt library that segfaults there.localstack/localstackto4.14.0, the last image that starts without a license.Why
Running this repository with a current Deno failed in two independent ways, and neither was visible in CI because every job goes through
nix develop, where the pinned nixpkgs still shipped Deno 2.6.6.Type checking (Deno 2.8.0 and later).
setIntervalnow returnsTimeoutrather thannumber, so#timerId: numberinsrc/cli/progress.tsfails to type check. Becausedeno testtype checks too, this took down the entire test suite and not justdeno check.subprocess.tswas already inferring its timeout id fromsetTimeout; the spinner was the single place that had not.Test deadlock (Deno 2.9.2 and later).
roundTrip()inserializer_test.tswrote to aCborSequenceEncoderStreamand only afterwards attached a reader to thepipeThrough'd output. On Deno 2.9.2 and later that write never resolves, so every serializer test hung. This is a Deno regression, not a@std/cborissue — it reproduces with two bareTransformStreams, works on Deno 2.9.1 and earlier, works on Node.js v24, and is still present on the latest canary. Reported upstream as denoland/deno#36790. The production path was never affected:encodeToCborinsubprocess.tsalready collects its output concurrently, andconnectIpcattaches its reader right afterpipeThrough. Only the test helper deviated, so the fix aligns it with the production code rather than working around the runtime.The nixpkgs pin. With CI eight months behind on the toolchain, both of the above were invisible to the project while being immediately visible to anyone with a current Deno. Bumping the pin to Deno 2.9.5 makes CI exercise the version users actually have. The two fixes land first so the tree type checks and tests green at every commit.
SQLite. The nixpkgs bump then surfaced a segfault:
@db/sqlitedownloads a prebuiltlibsqlite3.soand, against the newer glibc, dlopening it kills the process. Every scenario subprocess died before running a step, including scenarios that never touch SQLite — the root module re-exports the SQLite client (mod.ts→client.ts→sql.ts→sql/sqlite.ts), and@db/sqlitedlopens as it evaluates. SettingDENO_SQLITE_PATHto the nixpkgs SQLite makes@db/sqliteskip the prebuilt entirely. It is set in both the devShell (used bytestandscenario-test) and the package wrapper (used byscenario-test-nix). Deno itself is not at fault here — 2.9.5 loads the same library fine on macOS.That leaves the underlying design problem — importing
@probitas/probitasat all pays for the SQLite FFI — which is fixed in probitas-test/probitas-packages#23 by loading@db/sqliteon first use. This PR does not depend on that landing; the two are complementary.LocalStack. Bumping the pin surfaced a second, unrelated breakage: LocalStack merged its community and pro images, and every image published after that merge quits with exit code 55 unless
LOCALSTACK_AUTH_TOKENis set.test,scenario-testandscenario-test-nixall died at container init before a single test ran, andcompose.yamlhits the same wall locally. This too was hidden by the gap in CI runs — the last run onmainpredates the change.4.14.0is the last community release and needs no token. It is a stopgap that freezes the SQS test environment at February 2026; #109 tracks replacing LocalStack with ElasticMQ, which needs no license at all.Test Plan
deno task verifyon Deno 2.6.6 (the previously pinned toolchain) — 45 passed, 0 faileddeno task verifyon Deno 2.9.5 (the newly pinned toolchain) — 45 passed, 0 failednix build, and the built binary runs the SQLite scenario end to end — 10 steps passedcheckjob green on the updated pin (this is the job the type fix unblocks)test,scenario-testandscenario-test-nixgreen — these could not run at all before the LocalStack pin, and the segfault only reproduces on Linux, so CI is the first real exercise of both fixes🤖 Generated with Claude Code
https://claude.ai/code/session_01E6iErRVYiy7LzNGLT5Vzh1