---
title: "Deno 2.9 Ships 1.98x Faster Cold Start, 2.2-3.1x Less RSS Under Load, Default-On npm Minimum Release Age, No-Downgrade Trust Policy, and Built-In Snapshot Testing"
description: "Deno 2.9 (Bartek Iwańczuk, published 2026-06-25 on deno.com/blog/v2.9) is the largest Deno release of the cycle. Cold start drops from 34.2 ms to 17.3 ms (1.98x), peak RSS on the Deno.serve realworld workload drops 2.2x (142 MB → 64 MB) and 3.1x on 1 MiB bodies (197 MB → 63 MB), and Deno.serve throughput climbs 1.27x realworld (56.8k → 72.4k req/s), 1.11x plaintext, and 1.18x on 1 MiB bodies. Supply chain hardening: npm minimum-release-age is enabled by default with a 24h window (PR #35458), and a new opt-in no-downgrade trust policy (PR #34927) refuses to resolve any version whose trust evidence (staged publish, trusted publishing, provenance attestation) is weaker than the strongest evidence on any earlier-published version of the same package. Test runner parity: built-in t.assertSnapshot() (#35139), Deno.test.each (#34938), --shard for CI fan-out (#35057), retry and repeats (#35053), change-aware --changed and --related (#35199), and coverage thresholds (#35056). Lockfile interop: deno install seeds deno.lock from package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock (#34296, #35330, #35346, #35350, #35394), pnpm-workspace.yaml auto-migrates to deno.json / package.json (#34993), and git merge conflict markers in deno.lock auto-resolve (#34726). Plus: deno desktop graduates from experimental (the June 16 PR #33441), deno link / deno unlink / deno list / deno watch subcommands, stable --unsafe-proto (#34738), Web Locks API (#31166), Happy Eyeballs v2 (RFC 8305) (#31726), navigator.userAgentData (#34743), the WebCrypto Modern Algorithms proposal (ML-KEM, ML-DSA, SLH-DSA, ChaCha20-Poly1305, SHA-3 family, KMAC, Argon2) (#34447, #34448, #34914, #35223), Node 26.3.0 compat (#34746, #34747), Node-API v10 (#35270), and CSS module imports under --unstable-raw-imports (#35093). 165+ PRs land in this cycle."
date: 2026-06-26
image: "/images/heroes/2026-06-26--deno-2-9-cold-start-supply-chain-tests.png"
author: lschvn
tags: ["runtimes", "security", "performance"]
tldr:
  - "Deno 2.9 ([deno.com/blog/v2.9](https://deno.com/blog/v2.9), published 2026-06-25 by Bartek Iwańczuk) is the largest Deno release of the cycle. Cold start drops from 34.2 ms to 17.3 ms (1.98x), peak RSS on Deno.serve drops 2.2x realworld (142 MB → 64 MB) and 3.1x on 1 MiB bodies (197 MB → 63 MB), and Deno.serve throughput climbs 1.27x realworld (56.8k → 72.4k req/s), 1.11x plaintext, 1.18x on 1 MiB bodies. The perf work spans lazy-loading node: globals out of the snapshot, gating Node bootstrap to Node workers, a V8 code cache for lazy ESM, a minified snapshot (#34450, #35373, #35338, #35183), a Deno-owned HTTP/1.1 serving path (#34446), and Rust ports of crypto.subtle (#34966) and console / Deno.inspect (#35087)."
  - "Supply chain hardening is the other headline. npm minimum-release-age is enabled by default with a 24h window ([PR #35458](https://github.com/denoland/deno/pull/35458)), so a freshly-published, potentially-compromised version never lands in your dependency tree the moment it appears. A new opt-in no-downgrade trust policy ([PR #34927](https://github.com/denoland/deno/pull/34927)) ranks each package version by trust evidence (staged publishing, trusted publishing, provenance attestation) and refuses to resolve a version whose evidence is weaker than the strongest on any earlier-published version, the same shape as the [August 2025 s1ngularity attack](https://socket.dev/blog/npm-package-changelog-s1ngularity-nx-attack) that the [Axios npm supply chain attack](/articles/2026-03-31--axios-npm-supply-chain-attack) and [Red Hat / Shai-Hulud npm attack](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud) both extended. The release also resolves git merge conflicts in deno.lock automatically (#34726) and lets Deno seed a fresh deno.lock from package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock on the first install (#34296, #35330, #35346, #35350, #35394), with pnpm-workspace.yaml auto-migrating to deno.json / package.json (#34993)."
  - "Test runner parity closes the Vitest / Jest feature gap. The test context gains built-in t.assertSnapshot() with the @std/testing/snapshot format ([PR #35139](https://github.com/denoland/deno/pull/35139)), Deno.test.each registers one independently-filterable test per row of inputs (#34938), --shard=fan-out a CI matrix (#35057), retry and repeats detect flaky tests (#35053), --changed and --related do dependency-aware change-aware test selection (#35199), and coverage thresholds exit non-zero when line / branch / function coverage drops below target (#35056). The Deno desktop subcommand that landed as a PR on June 16 ([article](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow), [PR #33441](https://github.com/denoland/deno/pull/33441)) ships in 2.9 as the first tagged build with the feature, plus deno link / deno unlink / deno list / deno watch subcommands, stable --unsafe-proto (#34738), Web Locks API (#31166), Happy Eyeballs v2 (#31726), navigator.userAgentData (#34743), the WebCrypto Modern Algorithms proposal (ML-KEM, ML-DSA, SLH-DSA, ChaCha20-Poly1305, SHA-3, KMAC, Argon2) (#34447, #34448, #34914, #35223), Node 26.3.0 compat (#34746, #34747), Node-API v10 (#35270), CSS module imports under --unstable-raw-imports (#35093), deno fmt rebuilt on lax-markup / lax-css / lax-sql, deno compile --bundle and --include-as-is, deno bundle --declaration, deno task input-based caching, JSR deps in node_modules, preferPackageJson, and workspace node_modules with .bin."
faq:
  - question: "What is the headline change in Deno 2.9?"
    answer: "Deno 2.9 has three roughly co-equal headlines. The performance work cuts cold start to 17.3 ms (1.98x), drops peak RSS on Deno.serve 2.2-3.1x, and lifts Deno.serve throughput 1.11x to 1.27x depending on the workload, the largest single-cycle perf win since Deno 1.x. The supply chain hardening makes npm minimum-release-age default-on with a 24h window and adds an opt-in no-downgrade trust policy that refuses to resolve a package version whose trust evidence is weaker than the strongest on any earlier-published version of the same package. The test runner work closes the Vitest / Jest feature gap with built-in snapshot testing, Deno.test.each, --shard, retry and repeats, --changed and --related, and coverage thresholds. Plus the [deno desktop subcommand](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow) that landed as a PR on June 16 ships in 2.9 as the first tagged build."
  - question: "Where do the 1.98x cold start and 2-3x memory wins come from?"
    answer: "The cold start win comes from four primary sources ([#34450](https://github.com/denoland/deno/pull/34450), [#35373](https://github.com/denoland/deno/pull/35373), [#35338](https://github.com/denoland/deno/pull/35338), [#35183](https://github.com/denoland/deno/pull/35183)): lazy-loading node: globals out of the snapshot so the snapshot is smaller and the node bootstrap only happens when needed, gating the eager Node bootstrap to Node workers (the main isolate no longer pays the bootstrap cost for code that never touches node:), a V8 code cache for residual lazy-loaded ESM modules so the second startup skips the parse-and-compile step, and a minified snapshot. On macOS, chained fixups trim additional pre-main time ([#35409](https://github.com/denoland/deno/pull/35409)). The HTTP throughput gains come from a new Deno-owned HTTP/1.1 serving path that replaces the hyper-based path Deno has shipped with since 1.x ([#34446](https://github.com/denoland/deno/pull/34446)), and from moving crypto.subtle ([#34966](https://github.com/denoland/deno/pull/34966)) and console / Deno.inspect ([#35087](https://github.com/denoland/deno/pull/35087)) from JavaScript to Rust. The memory flatness under load is the most consequential change operationally: a server that previously grew with the workload now sits around 62 MB no matter what it is serving, which is what lets the same machine run far more concurrent Deno.serve instances."
  - question: "What is npm minimum-release-age, and why is it default-on now?"
    answer: "npm minimum-release-age is a Deno-only setting that refuses to install any npm package version younger than a configured age. Deno first shipped the setting in 2.6, and most teams that turned it on configured a 24h to 72h window. The premise is that a large class of npm supply chain attacks is caught simply by waiting: a malicious version is usually detected and unpublished within a day or two of being released, so a 24h window rejects most of them at install time. In 2.9 the setting is enabled by default with a 24h window ([PR #35458](https://github.com/denoland/deno/pull/35458)). The default sits at the bottom of the precedence chain, so anything you set explicitly in `.npmrc` wins (`min-release-age=72h` extends the window, `min-release-age=0` opts out). This is the same defense that [pnpm shipped in 9.x](https://pnpm.io/npmrc#minimizereleaseage) and that [Bun shipped in 1.x](https://bun.com/docs/runtime/modules#minimum-release-age). Deno is the third major package manager to default it on."
  - question: "What is the no-downgrade trust policy, and how does it work?"
    answer: "The no-downgrade trust policy ([PR #34927](https://github.com/denoland/deno/pull/34927)) is an opt-in npm trust policy that defends against stolen-maintainer-token attacks. It ranks how each package version was published, in this order of decreasing strength: staged publishing (a maintainer approving a publish with a live 2FA challenge), trusted publishing backed by a provenance attestation, a provenance attestation on its own, and finally a plain token publish. With `trust-policy=no-downgrade` in `.npmrc`, Deno refuses to resolve a version whose trust evidence is weaker than the strongest evidence on any earlier-published version of the same package, compared by publish date. If a package has consistently shipped through trusted publishing or with provenance and a later version suddenly appears as a plain token publish (the hallmark of a compromised maintainer token, as in the August 2025 s1ngularity incident), the install becomes a hard error instead of a silent downgrade. The policy follows [pnpm's design](https://pnpm.io/npmrc#trustpolicy), and Deno ships two escape hatches that mirror pnpm: `trust-policy-ignore-after` (in minutes) skips the check for older, genuinely pre-provenance releases, and `trust-policy-exclude[]=package` exempts named packages. The policy is off by default because provenance and trusted publishing are still unevenly adopted across the registry."
  - question: "What does the test runner gain in 2.9?"
    answer: "Six pieces close the Vitest / Jest feature gap. Built-in snapshot testing lands in the test context as `t.assertSnapshot()`, using the same format and serializer as `@std/testing/snapshot`, with no import required ([#35139](https://github.com/denoland/deno/pull/35139)). `Deno.test.each` registers one independently-filterable test per row of inputs and supports printf-style name interpolation (%s, %i/%d, %f, %j, %o, $key) ([#34938](https://github.com/denoland/deno/pull/34938)). `deno test --shard=index/count` splits the discovered test files into balanced groups for CI fan-out ([#35057](https://github.com/denoland/deno/pull/35057)). `retry: N` and `repeats: N` options catch flaky tests and stability-sensitive tests, with a test that only passes after a retry reported as flaky in the summary so the signal is not silently lost ([#35053](https://github.com/denoland/deno/pull/35053)). `deno test --changed` and `deno test --related=path` do dependency-aware change-aware test selection that walks the module graph across workspace members and is conservative enough that changing config, lockfile, import map, or package.json disables filtering ([#35199](https://github.com/denoland/deno/pull/35199)). And coverage thresholds exit non-zero when line / branch / function coverage drops below target, configurable per-metric in deno.json ([#35056](https://github.com/denoland/deno/pull/35056))."
  - question: "How does the lockfile interop work?"
    answer: "On the first `deno install` in a project that has a package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock but no deno.lock, Deno seeds a fresh deno.lock straight from it, carrying over the exact resolved versions and integrity hashes ([PRs #34296](https://github.com/denoland/deno/pull/34296), [#35330](https://github.com/denoland/deno/pull/35330), [#35346](https://github.com/denoland/deno/pull/35346), [#35350](https://github.com/denoland/deno/pull/35350), [#35394](https://github.com/denoland/deno/pull/35394)). There is no re-resolution and no surprise upgrades: the versions the user was running under npm are the versions they run under Deno. For pnpm workspaces, the separate `pnpm-workspace.yaml` is auto-migrated into `package.json` (or `deno.json`) without disturbing comments or existing fields ([#34993](https://github.com/denoland/deno/pull/34993)). For a `deno.lock` that already contains git merge conflict markers from a botched rebase, Deno now resolves them automatically, unioning the additive sections and taking the higher version on genuine specifier conflicts ([#34726](https://github.com/denoland/deno/pull/34726)). When a node_modules directory is in use, the new opt-in `jsrDepsInNodeModules` setting installs `jsr:` dependencies into it through JSR's npm compatibility registry, matching how pnpm and npm already handle JSR packages ([#35029](https://github.com/denoland/deno/pull/35029))."
  - question: "What does deno desktop look like in 2.9?"
    answer: "The deno desktop subcommand that [merged as PR #33441 on June 16](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow) ships in Deno 2.9 as the first tagged build. The June 16 PR coverage is the architectural piece (WEF backend, CEF default, webview / raw alternates, Deno.BrowserWindow API, framework auto-detection for Next/Astro/Fresh/Remix/Nuxt/SvelteKit/SolidStart/TanStack Start/Vite SSR, CDP multiplexer for unified DevTools across two V8 isolates, auto-updater with bsdiff patches, cross-compile). The 2.9 release adds the practical pieces that turned the PR into something you can ship: a default UI backend of webview (per [#35442](https://github.com/denoland/deno/pull/35442)) so binaries stay small and launch fast, native Wayland instead of XWayland on Wayland systems ([#35485](https://github.com/denoland/deno/pull/35485)), Bluetooth usage descriptions and macOS desktop Info.plist keys ([#35472](https://github.com/denoland/deno/pull/35472), [#35484](https://github.com/denoland/deno/pull/35484)), bundled libc++ symbols hidden so the desktop runtime can be dlopen'd on Linux ([#35424](https://github.com/denoland/deno/pull/35424)), Linux .deb and .rpm installer output formats ([#35296](https://github.com/denoland/deno/pull/35296)), Windows .msi installer output format ([#35378](https://github.com/denoland/deno/pull/35378)), autodetect Vite framework ([#35470](https://github.com/denoland/deno/pull/35470)), and --compress for self-extracting app bundles ([#35420](https://github.com/denoland/deno/pull/35420)). The full guide lives at [docs.deno.com/runtime/manual/desktop](https://docs.deno.com/runtime/manual/desktop), and `denidian`, a note-taking app built with deno desktop, is published alongside as the reference example."
  - question: "What new web platform features ship in 2.9?"
    answer: "Six additions. The Web Locks API ships in full ([#31166](https://github.com/denoland/deno/pull/31166)), letting you coordinate access to a named resource across async tasks and workers through `navigator.locks.request(name, async lock => { ... })`, with shared vs exclusive modes, ifAvailable, steal, an AbortSignal, and `navigator.locks.query()` to inspect held and pending locks. Happy Eyeballs v2 ([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305)) lands in `Deno.connect` and `Deno.connectTls` ([#31726](https://github.com/denoland/deno/pull/31726)), racing IPv6 and IPv4 addresses on dual-stack networks for faster, more reliable connections, on by default with `autoSelectFamily: false` to opt out. `navigator.userAgentData` ships in both window and worker scopes ([#34743](https://github.com/denoland/deno/pull/34743)). `RequestInit` accepts the Fetch-standard `priority` member (`auto`, `high`, `low`) ([#34716](https://github.com/denoland/deno/pull/34716)). `Deno.watchFs` supports an `ignore` option for paths like `.git` or `build` output ([#31582](https://github.com/denoland/deno/pull/31582)). And `process.kill` on the current process no longer requires `--allow-run` ([#34382](https://github.com/denoland/deno/pull/34382))."
  - question: "What is the WebCrypto Modern Algorithms work in 2.9?"
    answer: "Deno 2.9 implements the [Modern Algorithms in the Web Cryptography API proposal](https://wicg.github.io/webcrypto-modern-algos/) starting with NIST's post-quantum algorithms. ML-KEM (FIPS 203) key encapsulation: ML-KEM-512, ML-KEM-768, ML-KEM-1024 ([#34447](https://github.com/denoland/deno/pull/34447)). ML-DSA (FIPS 204) signatures: ML-DSA-44, ML-DSA-65, ML-DSA-87, including JWK import/export ([#34448](https://github.com/denoland/deno/pull/34448), [#34914](https://github.com/denoland/deno/pull/34914)). SLH-DSA (FIPS 205) signatures, all twelve parameter sets ([#35223](https://github.com/denoland/deno/pull/35223)). ML-KEM adds four new crypto.subtle methods: encapsulateKey / encapsulateBits and decapsulateKey / decapsulateBits. Beyond post-quantum, 2.9 adds ChaCha20-Poly1305 AEAD ([#34417](https://github.com/denoland/deno/pull/34417)), the SHA-3 family and XOFs (SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256, cSHAKE, TurboSHAKE, KangarooTwelve), KMAC, and Argon2 key derivation. A new synchronous SubtleCrypto.supports() feature-detection method ([#34903](https://github.com/denoland/deno/pull/34903)) lets user code test for support at runtime. The entire crypto.subtle implementation was ported from JavaScript to Rust ([#34966](https://github.com/denoland/deno/pull/34966)), trimming per-call overhead with no change in behavior. This is the same family of work that the [Node.js 24.18.0 'Krypton' LTS Web Crypto piece](/articles/2026-06-24--node-js-24-18-krypton-lts-buffer-pool-turboshake) covered for Node 24."
  - question: "What Node.js compatibility changes ship in 2.9?"
    answer: "Deno 2.9 advances its Node.js compatibility target to Node 26. The reported `process.version` moves to `v26.3.0` ([#34747](https://github.com/denoland/deno/pull/34747)), and the node-compat test suite Deno runs against is bumped to 26.3.0 ([#34746](https://github.com/denoland/deno/pull/34746)). Bare Node builtins now resolve without configuration: `import fs` and `import path` map to `node:fs` / `node:path` unconditionally, with no `--unstable-bare-node-builtins` flag ([#33316](https://github.com/denoland/deno/pull/33316)). This also fixes a bug where a node_modules package could shadow a builtin; builtins now always win, while `deno.json` imports and `package.json` dependencies mappings still take precedence. Worth calling out: `node:test` gained `mock.module()` and `mock.timers` ([#35329](https://github.com/denoland/deno/pull/35329), [#33946](https://github.com/denoland/deno/pull/33946)), `t.assert.fileSnapshot()` ([#35478](https://github.com/denoland/deno/pull/35478)), and `TestContext.runOnly()` ([#35158](https://github.com/denoland/deno/pull/35158)), and now fails on unhandled rejections, enforces timeouts, and runs hooks in the correct order ([#35297](https://github.com/denoland/deno/pull/35297), [#35393](https://github.com/denoland/deno/pull/35393)). More runtime APIs: `process.resourceUsage()` ([#35468](https://github.com/denoland/deno/pull/35468)) and `worker_threads.isInternalThread` ([#35234](https://github.com/denoland/deno/pull/35234)) are now implemented, and `AsyncLocalStorage` context is preserved across `node:net` callbacks ([#35237](https://github.com/denoland/deno/pull/35237)). Deno's NAPI implementation now reports version 10 ([#35270](https://github.com/denoland/deno/pull/35270))."
  - question: "What should Deno 2.8 users and Node users do today?"
    answer: "Deno 2.8 users should upgrade immediately: `deno upgrade` pulls 2.9 in place, and the cold start and memory wins are large enough that any production workload benefits. The supply chain defaults are also strictly better than the previous behavior: minimum-release-age at 24h catches a class of attacks the previous default did not, and the no-downgrade trust policy is opt-in for teams that want to harden further. The lockfile interop means a Node project can switch with a single `deno install` and get a seeded deno.lock straight from the existing package-lock.json, pnpm-lock.yaml, yarn.lock, or bun.lock, then run unmodified through `deno task` and the `node` shim Deno puts on PATH when no real Node is installed. Teams that hit a regression can pin Deno to a specific version with `deno upgrade --version 2.8.3` or set the version through the standard version manager (e.g., `vfox`, `asdf`, `mise`). For Node users, the WebCrypto Modern Algorithms work is the same family of work as the [Node.js 24.18.0 'Krypton' LTS Web Crypto piece](/articles/2026-06-24--node-js-24-18-krypton-lts-buffer-pool-turboshake), and the [Node.js 26.4.0 'Current'](/articles/2026-06-25--node-js-26-4-current-vfs-loader-package-maps) release picks up the same ML-DSA / ML-KEM / ChaCha20-Poly1305 / AES-KW work in a different shape; teams evaluating Deno as an alternative to Node have one more reason to give it a closer look on a real workload."
---

[Deno 2.9](https://deno.com/blog/v2.9) shipped on 2026-06-25, published by Bartek Iwańczuk with the [release notes on deno.com](https://deno.com/blog/v2.9) and the [GitHub releases API listing v2.9.0](https://github.com/denoland/deno/releases/tag/v2.9.0). It is the largest Deno release of the cycle and the first tagged build that includes the [deno desktop subcommand](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow) (the [June 16 PR #33441](https://github.com/denoland/deno/pull/33441)) as well as a long list of supply chain, performance, test runner, web platform, and Node.js compatibility changes. The blog post opens with three numbers that anchor the release: cold start drops from 34.2 ms to 17.3 ms (1.98x), peak resident set on Deno.serve drops 2.2x on the realworld workload and 3.1x on 1 MiB bodies, and Deno.serve throughput climbs 1.27x realworld, 1.11x plaintext, and 1.18x on 1 MiB bodies. Behind those numbers are four primary perf changes ([#34450](https://github.com/denoland/deno/pull/34450), [#35373](https://github.com/denoland/deno/pull/35373), [#35338](https://github.com/denoland/deno/pull/35338), [#35183](https://github.com/denoland/deno/pull/35183)), plus a new Deno-owned HTTP/1.1 serving path ([#34446](https://github.com/denoland/deno/pull/34446)) and Rust ports of crypto.subtle ([#34966](https://github.com/denoland/deno/pull/34966)) and console / Deno.inspect ([#35087](https://github.com/denoland/deno/pull/35087)).

The 2.9 release follows [Deno 2.8 on June 1](/articles/2026-06-01--deno-2-8-audit-fix-ci-pack-subcommands), which focused on audit fixes, CI tooling, and the `pack` subcommand. The 2.9 cycle is a different shape: 165+ PRs land, the perf wins are the largest single-cycle jump in two years, and the supply chain work closes the gap with [pnpm's `minimumReleaseAge`](https://pnpm.io/npmrc#minimizereleaseage) and [pnpm's `trustPolicy`](https://pnpm.io/npmrc#trustpolicy) defaults.

## Cold start, memory, and throughput

The headline numbers come from Deno's own benchmark harness, which runs three Deno.serve workloads at concurrency 100 against Deno 2.8.0 on a dedicated x86_64 Linux box, with server and load generator pinned to disjoint cores and oha median of 3 runs. Cold start is the mean of 150 hyperfine runs of a hello-world program. The full set:

| Workload | Deno 2.8 | Deno 2.9 | Change |
| --- | --- | --- | --- |
| Cold start (lower is better) | 34.2 ms | 17.3 ms | 1.98x faster |
| Deno.serve realworld (req/s, higher is better) | 56.8k | 72.4k | 1.27x |
| Deno.serve plaintext (req/s) | 77.0k | 85.6k | 1.11x |
| Deno.serve 1 MiB body (req/s) | 1,617 | 1,907 | 1.18x |
| RSS, realworld (lower is better) | 142 MB | 64 MB | 2.2x less memory |
| RSS, 1 MiB body | 197 MB | 63 MB | 3.1x less memory |

The cold start drop is the most striking single number. The win comes from four primary changes. First, lazy-loading `node:` globals out of the snapshot so the snapshot itself is smaller ([#34450](https://github.com/denoland/deno/pull/34450)). Second, gating the eager Node bootstrap to Node workers, so the main isolate no longer pays the bootstrap cost for code that never touches `node:` ([#35373](https://github.com/denoland/deno/pull/35373)). Third, a V8 code cache for residual lazy-loaded ESM modules so the second startup skips the parse-and-compile step ([#35338](https://github.com/denoland/deno/pull/35338)). Fourth, a minified snapshot that compresses the on-disk image ([#35183](https://github.com/denoland/deno/pull/35183)). On macOS, chained fixups trim additional pre-main time ([#35409](https://github.com/denoland/deno/pull/35409)).

The HTTP throughput gains ride on a new Deno-owned HTTP/1.1 serving path that replaces the hyper-based path Deno has shipped since 1.x ([#34446](https://github.com/denoland/deno/pull/34446)). The two Rust ports are smaller but consequential: crypto.subtle ([#34966](https://github.com/denoland/deno/pull/34966)) and console / Deno.inspect ([#35087](https://github.com/denoland/deno/pull/35087)) move from JavaScript into Rust, trimming per-call overhead with no change in behavior. The WebCrypto port is the prerequisite for the [WebCrypto Modern Algorithms work](#webcrypto-modern-algorithms) that lands the same release.

The memory flatness under load is the most consequential change operationally. In 2.8, RSS grew with the workload, from roughly 94 MB serving plaintext up to 197 MB streaming 1 MiB bodies. In 2.9 it stays essentially flat, holding around 62 MB no matter what the server is doing. That works out to 2.2x less peak RSS on the realworld workload and 3.1x less on 1 MiB bodies, so the same machine can run far more concurrent Deno.serve instances before it runs out of headroom. The team flags this as the standout of the cycle.

## Supply chain hardening

The second headline is supply chain hardening, and it is the part of 2.9 that maps most directly onto the [Axios npm supply chain attack](/articles/2026-03-31--axios-npm-supply-chain-attack) from March and the [Red Hat / Shai-Hulud npm attack](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud) from early June.

**npm minimum-release-age, default-on.** [PR #35458](https://github.com/denoland/deno/pull/35458) enables minimum-release-age by default with a 24h window. The setting was introduced in Deno 2.6 and was already a documented feature, but in 2.9 it is on by default. The premise is straightforward: a large class of npm supply chain attacks is caught simply by waiting, because a malicious version is usually detected and unpublished within a day or two of being released. A 24h window rejects most of them at install time. The default sits at the bottom of the precedence chain, so anything set explicitly in `.npmrc` wins: `min-release-age=72h` extends the window, `min-release-age=0` opts out entirely. This is the same defense that [pnpm shipped in 9.x](https://pnpm.io/npmrc#minimizereleaseage) and that [Bun shipped in 1.x](https://bun.com/docs/runtime/modules#minimum-release-age). Deno is the third major package manager to default it on.

**no-downgrade trust policy, opt-in.** [PR #34927](https://github.com/denoland/deno/pull/34927) adds the trust policy Deno has been missing. It ranks each package version by trust evidence: staged publishing (a maintainer approving a publish with a live 2FA challenge), trusted publishing backed by a provenance attestation, a provenance attestation on its own, and finally a plain token publish. With `trust-policy=no-downgrade` in `.npmrc`, Deno refuses to resolve a version whose trust evidence is weaker than the strongest evidence on any earlier-published version of the same package, compared by publish date. If a package has consistently shipped through trusted publishing or with provenance and a later version suddenly appears as a plain token publish (the hallmark of a compromised maintainer token, as in the August 2025 s1ngularity incident that extended into the [Axios npm supply chain attack](/articles/2026-03-31--axios-npm-supply-chain-attack) and the [Red Hat / Shai-Hulud npm attack](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud)), the install becomes a hard error instead of a silent downgrade. The policy follows [pnpm's design](https://pnpm.io/npmrc#trustpolicy), and Deno ships two escape hatches that mirror pnpm: `trust-policy-ignore-after` (in minutes) skips the check for older, genuinely pre-provenance releases, and `trust-policy-exclude[]=package` exempts named packages. The policy is off by default because provenance and trusted publishing are still unevenly adopted across the registry.

**Lockfile interop.** The supply chain story extends to lockfiles. On the first `deno install` in a project that has a `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock` but no `deno.lock`, Deno seeds a fresh deno.lock straight from it, carrying over the exact resolved versions and integrity hashes ([#34296](https://github.com/denoland/deno/pull/34296), [#35330](https://github.com/denoland/deno/pull/35330), [#35346](https://github.com/denoland/deno/pull/35346), [#35350](https://github.com/denoland/deno/pull/35350), [#35394](https://github.com/denoland/deno/pull/35394)). For pnpm workspaces, the separate `pnpm-workspace.yaml` is auto-migrated into `package.json` (or `deno.json`) without disturbing comments or existing fields ([#34993](https://github.com/denoland/deno/pull/34993)). For a `deno.lock` that already contains git merge conflict markers from a botched rebase, Deno now resolves them automatically, unioning the additive sections and taking the higher version on genuine specifier conflicts ([#34726](https://github.com/denoland/deno/pull/34726)). The combination means switching a Node project to Deno takes a couple of commands and preserves the existing dependency graph exactly.

## Test runner parity with Vitest / Jest

The test runner work closes the Vitest / Jest feature gap that has been the most-cited reason teams stick with a Node-hosted test framework even when they like Deno for production code.

**Built-in snapshot testing.** [PR #35139](https://github.com/denoland/deno/pull/35139) adds `t.assertSnapshot()` to the test context, using the same format and serializer as `@std/testing/snapshot` with no import required. Snapshots are written to `__snapshots__/test_file.snap` next to the test; on a mismatch the runner prints a diff and tells you how to update (`deno test --update-snapshots`). Default-location snapshots need no read/write permissions (the runner manages them), and stale entries are pruned automatically when a full run updates them. Snapshot testing also works through `node:test`, via `t.assert.fileSnapshot()` ([#35478](https://github.com/denoland/deno/pull/35478)).

**Deno.test.each.** [PR #34938](https://github.com/denoland/deno/pull/34938) registers one real, independently-filterable test per row of inputs. Array cases are spread as positional arguments; object cases are passed as a single argument and can be interpolated into the test name with `$key`. Name templates support printf-style tokens (`%s`, `%i`/`%d`, `%f`, `%j`, `%o`), `%#` for the case index, and `$key.nested` for nested object access. `Deno.test.only.each` and `Deno.test.ignore.each` compose as expected.

**`deno test --shard`.** [PR #35057](https://github.com/denoland/deno/pull/35057) splits the discovered test files into balanced groups for CI fan-out. It drops straight into a GitHub Actions matrix; the index is 1-based, sharding happens before `--shuffle`, and over-sharding (more shards than files) simply leaves some shards empty and exits cleanly. The trade-off the PR is explicit about: the shard is selected at run time, after the module graph for the full suite has been built and type-checked, so every machine still pays the graph-build and type-check cost for the whole suite. Moving the shard pre-filter ahead of type-checking is a natural follow-up.

**Retry and repeats.** [PR #35053](https://github.com/denoland/deno/pull/35053) adds `retry: N` and `repeats: N` options either per-test or across the whole run. A test that only passes after a retry is reported as flaky in the summary, so the signal is not silently lost. Per-test options take precedence over the CLI flags (including an explicit `0` to opt a test out).

**Change-aware test selection.** [PR #35199](https://github.com/denoland/deno/pull/35199) adds `deno test --changed` (tests affected by uncommitted changes), `deno test --changed=origin/main` (tests affected since branching off main), and `deno test --related=src/util.ts` (tests that depend on a specific file). Selection is dependency-aware (it walks the module graph, across workspace members) and conservative: changing config, lockfile, import map, or package.json disables filtering and runs everything.

**Coverage thresholds.** [PR #35056](https://github.com/denoland/deno/pull/35056) lets coverage fail a run when it drops below a target, either via `--threshold=90` or configured per-metric in `deno.json` (`lines`, `branches`, `functions`). When the aggregate falls short, the command exits non-zero and tells you which metric missed.

## deno desktop ships as the first tagged build

The [deno desktop subcommand](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow) that [merged as PR #33441 on June 16](https://github.com/denoland/deno/pull/33441) ships in Deno 2.9 as the first tagged build with the feature. The June 16 article covers the architectural piece (WEF backend, CEF default, webview / raw alternates, `Deno.BrowserWindow` API, framework auto-detection for Next.js / Astro / Fresh / Remix / Nuxt / SvelteKit / SolidStart / TanStack Start / Vite SSR, CDP multiplexer for unified DevTools across two V8 isolates, auto-updater with bsdiff patches, cross-compile). The 2.9 release adds the practical pieces that turn the PR into something you can ship:

- **Default UI backend is webview** ([#35442](https://github.com/denoland/deno/pull/35442)), so binaries stay small and launch fast. `--backend cef` opts into bundled Chromium for guaranteed-identical rendering on every platform.
- **Native Wayland** instead of XWayland on Wayland systems ([#35485](https://github.com/denoland/deno/pull/35485)). The June 16 PR was X11-only and ran Wayland sessions through XWayland; the 2.9 cycle lifts that limit.
- **Linux .deb and .rpm installer output formats** ([#35296](https://github.com/denoland/deno/pull/35296)), produced from any host with no platform-specific packaging toolchain.
- **Windows .msi installer output format** ([#35378](https://github.com/denoland/deno/pull/35378)), the same.
- **Autodetect Vite framework** ([#35470](https://github.com/denoland/deno/pull/35470)), on top of the Next / Astro / Fresh / Remix / Nuxt / SvelteKit / SolidStart / TanStack Start list.
- **Bluetooth usage descriptions and macOS desktop Info.plist keys** ([#35472](https://github.com/denoland/deno/pull/35472), [#35484](https://github.com/denoland/deno/pull/35484)), for App Store submission readiness.
- **Bundled libc++ symbols hidden** ([#35424](https://github.com/denoland/deno/pull/35424)) so the desktop runtime can be dlopen'd on Linux.
- **--compress for self-extracting app bundles** ([#35420](https://github.com/denoland/deno/pull/35420)), which unpacks on first launch and shrinks artifact size for npm-heavy projects.

The full guide lives at [docs.deno.com/runtime/manual/desktop](https://docs.deno.com/runtime/manual/desktop), and `denidian`, a note-taking app built with deno desktop, is published alongside as the reference example.

## WebCrypto Modern Algorithms

The WebCrypto work is the same family of work as the [Node.js 24.18.0 'Krypton' LTS Web Crypto piece](/articles/2026-06-24--node-js-24-18-krypton-lts-buffer-pool-turboshake) and the [Node.js 26.4.0 'Current' WebCrypto cSHAKE work](/articles/2026-06-25--node-js-26-4-current-vfs-loader-package-maps): the [Modern Algorithms in the Web Cryptography API proposal](https://wicg.github.io/webcrypto-modern-algos/) starting with NIST's post-quantum algorithms.

- **ML-KEM (FIPS 203)** key encapsulation: ML-KEM-512, ML-KEM-768, ML-KEM-1024 ([#34447](https://github.com/denoland/deno/pull/34447)). Adds four new `crypto.subtle` methods: `encapsulateKey` / `encapsulateBits` and `decapsulateKey` / `decapsulateBits`.
- **ML-DSA (FIPS 204)** signatures: ML-DSA-44, ML-DSA-65, ML-DSA-87, including JWK import/export ([#34448](https://github.com/denoland/deno/pull/34448), [#34914](https://github.com/denoland/deno/pull/34914)).
- **SLH-DSA (FIPS 205)** signatures, all twelve parameter sets ([#35223](https://github.com/denoland/deno/pull/35223)).
- **ChaCha20-Poly1305** AEAD ([#34417](https://github.com/denoland/deno/pull/34417)).
- **SHA-3 family and XOFs**: SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256, cSHAKE, TurboSHAKE, KangarooTwelve.
- **KMAC** and **Argon2** key derivation.

A new synchronous `SubtleCrypto.supports()` feature-detection method ([#34903](https://github.com/denoland/deno/pull/34903)) lets user code test for support at runtime: `SubtleCrypto.supports("sign", "ML-DSA-65")` returns `true` on a 2.9 build. The entire `crypto.subtle` implementation was ported from JavaScript to Rust ([#34966](https://github.com/denoland/deno/pull/34966)), trimming per-call overhead with no change in behavior, which is the prerequisite for the post-quantum work landing in the same release.

## Node.js compatibility advances to 26

Deno 2.9 advances its Node.js compatibility target to Node 26. The reported `process.version` moves to `v26.3.0` ([#34747](https://github.com/denoland/deno/pull/34747)), and the node-compat test suite Deno runs against is bumped to 26.3.0 ([#34746](https://github.com/denoland/deno/pull/34746)). Bare Node builtins now resolve without configuration: `import fs` and `import path` map to `node:fs` / `node:path` unconditionally, with no `--unstable-bare-node-builtins` flag ([#33316](https://github.com/denoland/deno/pull/33316)). This also fixes a bug where a node_modules package could shadow a builtin; builtins now always win, while `deno.json` imports and `package.json` dependencies mappings still take precedence.

The `node:test` gains are a meaningful piece on their own: `mock.module()` and `mock.timers` ([#35329](https://github.com/denoland/deno/pull/35329), [#33946](https://github.com/denoland/deno/pull/33946)), `t.assert.fileSnapshot()` ([#35478](https://github.com/denoland/deno/pull/35478)), and `TestContext.runOnly()` ([#35158](https://github.com/denoland/deno/pull/35158)). The runner now fails on unhandled rejections, enforces timeouts, and runs hooks in the correct order ([#35297](https://github.com/denoland/deno/pull/35297), [#35393](https://github.com/denoland/deno/pull/35393)). More runtime APIs: `process.resourceUsage()` ([#35468](https://github.com/denoland/deno/pull/35468)) and `worker_threads.isInternalThread` ([#35234](https://github.com/denoland/deno/pull/35234)) are now implemented, and `AsyncLocalStorage` context is preserved across `node:net` callbacks ([#35237](https://github.com/denoland/deno/pull/35237)). Deno's NAPI implementation now reports version 10 ([#35270](https://github.com/denoland/deno/pull/35270)), in line with Node 26. The Web Storage / KV persistence ([#34618](https://github.com/denoland/deno/pull/34618)) for `deno compile` outputs and the `preferPackageJson` setting ([#35392](https://github.com/denoland/deno/pull/35392)) are the same compatibility-driven ergonomics work, now stabilized.

## Lockfile interop and dependency management

The lockfile work is the part of 2.9 most directly aimed at letting Node projects switch to Deno. The five lockfile-reading PRs ([#34296](https://github.com/denoland/deno/pull/34296), [#35330](https://github.com/denoland/deno/pull/35330), [#35346](https://github.com/denoland/deno/pull/35346), [#35350](https://github.com/denoland/deno/pull/35350), [#35394](https://github.com/denoland/deno/pull/35394)) together mean that running `deno install` in a project with a `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock` produces a deno.lock that preserves the exact resolved versions and integrity hashes from the existing lockfile. There is no re-resolution and no surprise upgrades: the versions the user was running under npm are the versions they run under Deno. From there `deno install` writes a `node_modules` directory Deno can run against, and `deno task` runs the `package.json` scripts they already have, so the rest of a team can keep working the way they do.

For pnpm workspaces, the separate `pnpm-workspace.yaml` is auto-migrated into `package.json` (or `deno.json`) without disturbing comments or existing fields ([#34993](https://github.com/denoland/deno/pull/34993)). Combined with the `catalog:` protocol Deno adopted in 2.8, centralized, shared dependency versions keep working after the move. For `deno.lock` files that already contain git merge conflict markers from a botched rebase, Deno now resolves them automatically, unioning the additive sections and taking the higher version on genuine specifier conflicts ([#34726](https://github.com/denoland/deno/pull/34726)). When a `node_modules` directory is in use, the new opt-in `jsrDepsInNodeModules` setting installs `jsr:` dependencies into it through JSR's npm compatibility registry ([#35029](https://github.com/denoland/deno/pull/35029)), matching how pnpm and npm already handle JSR packages.

The new dependency-management subcommands round out the picture. `deno link` and `deno unlink` ([#34359](https://github.com/denoland/deno/pull/34359)) give first-class CLI access to the `links` array in `deno.json` (the npm-link-style workflow for local JSR packages). The `links` field itself is stable in 2.9: it shipped under that name back in 2.3 and was never gated behind a runtime flag, so 2.9 simply drops the remaining unstable labeling ([#34996](https://github.com/denoland/deno/pull/34996)). `deno list` ([#34972](https://github.com/denoland/deno/pull/34972)) prints the dependencies a project declares in `deno.json` and `package.json` and resolves their versions, the equivalent of `npm ls` / `pnpm list`. `deno watch` ([#35301](https://github.com/denoland/deno/pull/35301)) is a short, more discoverable alias for `deno run --watch-hmr main.ts` that re-runs on file changes with hot module replacement, restarting if hot replacement fails.

## deno fmt on the lax engines

`deno fmt` rebuilds on the new lax formatting engines, which only ever move whitespace: they never reorder, requote, or drop a token, and they pass malformed input through instead of erroring. HTML, XML, and SVG are now formatted by lax-markup, and they format by default with no flag ([#35174](https://github.com/denoland/deno/pull/35174)). Component formats (Vue, Svelte, Astro, Vento, Nunjucks, Mustache) are available under `--unstable-component`. A 10 MB document that previously could not be formatted in 15 minutes now takes about a tenth of a second. CSS, SCSS, and Less are now formatted by lax-css (still under `--unstable-css`), which fixes a long list of parse errors and value-mangling bugs ([#35160](https://github.com/denoland/deno/pull/35160)). Note that the indented `.sass` syntax is no longer supported. SQL formatting (under `--unstable-sql`) is now powered by lax-sql, which produces canonical, dialect-agnostic output ([#35161](https://github.com/denoland/deno/pull/35161)).

Two new configuration options for JavaScript and JSON: `sortNamedImports` and `sortNamedExports` control how named specifiers are ordered (`caseInsensitive` (the default), `caseSensitive`, `maintain` (leave source order alone, handy for matching Biome's ordering)) ([#33313](https://github.com/denoland/deno/pull/33313)); and `json.trailingCommas` controls trailing commas in JSON and JSONC (`never`, `always`, `maintain`, `jsonc`) ([#33383](https://github.com/denoland/deno/pull/33383)). `deno fmt` now reads `.editorconfig` files and uses them to fill in any formatting options the user has not set explicitly, with precedence running CLI flags → deno.json → .editorconfig → built-in defaults ([#34071](https://github.com/denoland/deno/pull/34071)).

## deno task, deno compile, deno bundle

The task runner picks up input-based caching, concurrency control, and several new flags. Input-based caching: declare a task's inputs with `files` (and outputs with `output`), and Deno skips the task entirely when nothing relevant has changed, restoring any declared output artifacts straight from the cache ([#34509](https://github.com/denoland/deno/pull/34509)). The fingerprint includes the command, the contents of files matched by `files`, the values of any `env` variables listed, the fingerprints of the task's dependencies, the host OS, the CPU architecture, and the Deno version. Arguments and env are part of the key: `deno task build foo` and `deno task build bar` cache independently, and changing a listed env value invalidates the cache. Dependencies cascade (a task re-runs when one of its dependencies re-ran, even if its own inputs are unchanged). Safe by default: if the files globs match nothing, the task is treated as uncacheable and always runs, so a typo can never produce a false cache hit. npm scripts and tasks without a command are never cached.

Controlling concurrency: `--jobs` (short `-j`, alias `--concurrency`) caps how many tasks run at once in a workspace run; `--jobs 1` forces sequential execution; it overrides the `DENO_JOBS` environment variable and defaults to the number of available CPUs ([#35318](https://github.com/denoland/deno/pull/35318)). Other flags: `--if-present` exits 0 instead of erroring when the named task does not exist, matching npm ([#35315](https://github.com/denoland/deno/pull/35315)); `--env-file` loads a dotenv file into the tasks environment without forwarding the flag to every inner command ([#34508](https://github.com/denoland/deno/pull/34508)); exclusion groups in task-name wildcards (`deno task test:*(!e2e|interactive)`) run every `test:*` task except the excluded ones ([#34506](https://github.com/denoland/deno/pull/34506)).

`deno compile` gains `--include-as-is`, which embeds a file or directory into the executable's virtual filesystem without any module resolution or transpilation ([#32417](https://github.com/denoland/deno/pull/32417)). Where `--include` runs files through the module graph, `--include-as-is` is for assets and pre-built bundles you just want available via filesystem APIs at runtime. The two flags combine, so you can resolve some modules and embed others verbatim in the same build. Compiled binaries also get real persistent storage: a default `Deno.openKv()`, `localStorage`, and the caches API now persist to a per-app directory under the platform's app-data location instead of falling back to in-memory storage ([#34618](https://github.com/denoland/deno/pull/34618)). The storage identity is the new `--app-name` flag, which defaults to the output file name, so two binaries built with the same `--app-name` share a store. By default `deno compile` embeds the entire resolved `node_modules` tree into the binary; the new experimental `--bundle` flag instead runs the entrypoint through Deno's bundler first (tree-shaking and emitting a single module), which can dramatically shrink binaries for npm-heavy projects (a lodash hello-world dropped from 11.6 MB to 1.5 MB in Deno's own measurements). Pair it with `--minify` to shrink the embedded bundle further ([#34527](https://github.com/denoland/deno/pull/34527), [#34532](https://github.com/denoland/deno/pull/34532), [#34536](https://github.com/denoland/deno/pull/34536)). `deno compile` also picks up `--watch` mode ([#34860](https://github.com/denoland/deno/pull/34860)). `deno bundle` can now emit a rolled-up `.d.ts` alongside the bundled JavaScript with `--declaration` ([#33838](https://github.com/denoland/deno/pull/33838)), and understands the object form of npm's `package.json` `browser` field when bundling with `--platform browser` ([#34407](https://github.com/denoland/deno/pull/34407)).

## Web platform and runtime additions

Six additions to the web platform surface ship in 2.9. The [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) lands in full ([#31166](https://github.com/denoland/deno/pull/31166)), letting you coordinate access to a named resource across async tasks and workers through `navigator.locks.request("config", async lock => { /* exclusive access here until this callback resolves */ })`, with shared vs exclusive modes, `ifAvailable`, `steal`, an `AbortSignal`, and `navigator.locks.query()` to inspect held and pending locks. Happy Eyeballs v2 ([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305)) lands in `Deno.connect` and `Deno.connectTls` ([#31726](https://github.com/denoland/deno/pull/31726)), racing IPv6 and IPv4 addresses on dual-stack networks for faster, more reliable connections, on by default with `autoSelectFamily: false` to opt out or `autoSelectFamilyAttemptDelay` to tune the stagger (default 250ms). `navigator.userAgentData` ships in both window and worker scopes ([#34743](https://github.com/denoland/deno/pull/34743)). `RequestInit` accepts the Fetch-standard `priority` member (`auto`, `high`, `low`) ([#34716](https://github.com/denoland/deno/pull/34716)). `Deno.watchFs` supports an `ignore` option for paths like `.git` or `build` output ([#31582](https://github.com/denoland/deno/pull/31582)). `process.kill` on the current process no longer requires `--allow-run` ([#34382](https://github.com/denoland/deno/pull/34382)).

CSS module imports ship under `--unstable-raw-imports` ([#35093](https://github.com/denoland/deno/pull/35093)), matching the CSS module scripts web standard: `import sheet from "./styles.css" with { type: "css" };` returns a `CSSStyleSheet` instance, so the same code runs in Deno and in the browser without a bundler step. Stable `--unsafe-proto` ([#34738](https://github.com/denoland/deno/pull/34738), [#35192](https://github.com/denoland/deno/pull/35192)) replaces the unstable flag, and when a program crashes after touching the disabled `Object.prototype.__proto__` accessor, Deno suggests re-running with it. The WebAssembly ESM integration fix ([#34912](https://github.com/denoland/deno/pull/34912)) unwraps global exports from imported `.wasm` modules, matching the WebAssembly/ESM spec and Node. `Deno.serve` no longer compresses response bodies automatically ([#35253](https://github.com/denoland/deno/pull/35253), [#35486](https://github.com/denoland/deno/pull/35486)); enable it per server with `automaticCompression: true`, or process-wide with `DENO_SERVE_AUTOMATIC_COMPRESSION=1`. OpenTelemetry gains `OTEL_TRACES_SAMPLER` (with `OTEL_TRACES_SAMPLER_ARG`) for head-based sampling, `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT` and `OTEL_SPAN_EVENT_COUNT_LIMIT` for per-span caps, and auto-instrumentation that now also traces `node:http2` clients and servers ([#34764](https://github.com/denoland/deno/pull/34764), [#34787](https://github.com/denoland/deno/pull/34787), [#34795](https://github.com/denoland/deno/pull/34795), [#34510](https://github.com/denoland/deno/pull/34510)).

## What this release is doing to Deno's trajectory

The 2.9 cycle is the first time Deno has shipped three roughly co-equal headline categories in a single tagged build: a major perf jump (cold start, memory, throughput), a major supply chain hardening (default-on minimum-release-age, opt-in no-downgrade trust policy), and a major test runner feature drop (snapshot testing, parameterized tests, sharding, retries, change-aware selection, coverage thresholds). The desktop subcommand that [merged as PR #33441 on June 16](/articles/2026-06-16--deno-desktop-subcommand-wef-cef-browserwindow) finally ships as the first tagged build, the lockfile interop work means a Node project can switch to Deno with a couple of commands and a preserved dependency graph, and the WebCrypto Modern Algorithms work puts Deno on the same post-quantum cryptography track as Node 24 LTS and Node 26 Current.

The pieces that the team has flagged as the next Deno release candidates are visible from the PR descriptions: sharding ahead of type-checking ([#35057](https://github.com/denoland/deno/pull/35057)'s trade-off), Web Locks API expansion (timeouts, `steal` with timeout), the no-downgrade trust policy gaining per-package overrides beyond the existing `trust-policy-exclude[]` array, and `deno fmt` graduating the lax engines from unstable to stable. The full list of 165+ PRs in the 2.9 cycle is on the [Deno 2.9.0 GitHub release](https://github.com/denoland/deno/releases/tag/v2.9.0).