Skip to content

Fix Deno 2.8+/2.9.2+ incompatibilities and unbreak CI - #108

Merged
lambdalisue merged 5 commits into
mainfrom
fix/deno-2.9-compat
Sep 7, 2026
Merged

lambdalisue merged 5 commits into
mainfrom
fix/deno-2.9-compat

Conversation

@lambdalisue

@lambdalisue lambdalisue commented Sep 7, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Type the spinner timer id via ReturnType<typeof setInterval> instead of a hard-coded number, which Deno 2.8.0 broke when it changed setInterval to return Timeout.
  • Start reading the CBOR round-trip output before writing to it in serializer_test.ts, working around a Web Streams regression in Deno 2.9.2.
  • Move the pinned nixpkgs from 2026-01-30 to 2026-09-05, which takes the toolchain from Deno 2.6.6 to 2.9.5, and point DENO_SQLITE_PATH at the nixpkgs SQLite so @db/sqlite stops loading a prebuilt library that segfaults there.
  • Pin localstack/localstack to 4.14.0, the last image that starts without a license.
  • Keep the round-trip helper's started read from escaping as an uncaught error when a write fails (review feedback).

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). setInterval now returns Timeout rather than number, so #timerId: number in src/cli/progress.ts fails to type check. Because deno test type checks too, this took down the entire test suite and not just deno check. subprocess.ts was already inferring its timeout id from setTimeout; the spinner was the single place that had not.

Test deadlock (Deno 2.9.2 and later). roundTrip() in serializer_test.ts wrote to a CborSequenceEncoderStream and only afterwards attached a reader to the pipeThrough'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/cbor issue — it reproduces with two bare TransformStreams, 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: encodeToCbor in subprocess.ts already collects its output concurrently, and connectIpc attaches its reader right after pipeThrough. 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/sqlite downloads a prebuilt libsqlite3.so and, 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/sqlite dlopens as it evaluates. Setting DENO_SQLITE_PATH to the nixpkgs SQLite makes @db/sqlite skip the prebuilt entirely. It is set in both the devShell (used by test and scenario-test) and the package wrapper (used by scenario-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/probitas at all pays for the SQLite FFI — which is fixed in probitas-test/probitas-packages#23 by loading @db/sqlite on 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_TOKEN is set. test, scenario-test and scenario-test-nix all died at container init before a single test ran, and compose.yaml hits the same wall locally. This too was hidden by the gap in CI runs — the last run on main predates the change. 4.14.0 is 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 verify on Deno 2.6.6 (the previously pinned toolchain) — 45 passed, 0 failed
  • deno task verify on Deno 2.9.5 (the newly pinned toolchain) — 45 passed, 0 failed
  • nix build, and the built binary runs the SQLite scenario end to end — 10 steps passed
  • check job green on the updated pin (this is the job the type fix unblocks)
  • test, scenario-test and scenario-test-nix green — 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

lambdalisue and others added 2 commits September 7, 2026 22:23
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
Copilot AI lite review requested due to automatic review settings September 7, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.ts to begin reading before writing to avoid a Deno Web Streams deadlock.
  • Update the nixpkgs pin in flake.lock to 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.

Comment thread src/cli/_templates/serializer_test.ts
@lambdalisue lambdalisue changed the title Fix Deno 2.8+/2.9.2+ incompatibilities and update the nixpkgs pin Fix Deno 2.8+/2.9.2+ incompatibilities and unbreak CI Sep 7, 2026
lambdalisue and others added 3 commits September 7, 2026 23:41
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
@lambdalisue
lambdalisue merged commit 990ad1d into main Sep 7, 2026
4 checks passed
@lambdalisue
lambdalisue deleted the fix/deno-2.9-compat branch September 7, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants