chore: adopt pnpm 12
- Dominant language
- TypeScript
- Stars
- 3
- Forks
- 0
- Avg merge
- 29m
- Merged PRs (30d)
- 127
Description
## Summary
Assessed **pnpm 11.25.0 → 12.3.4** on 2026-09-06 against `coder/ai-sdk` at `c32f1fc6b00b9c1125c6bcf2ea6418ee650c53d6`. **Deferred**: the install/build/test toolchain works, but pnpm 12's default lockfile shape has an unresolved downstream dependency-scanner compatibility concern. Keep the current pnpm pin until security visibility is verified; this is not a dependency-code or Node compatibility failure.
## Blocker: unresolved support for the environment-first lockfile
A clean installation after updating `mise.toml`, `mise.lock`, and root `packageManager` to 12.3.4 prepends **101 lines** to `pnpm-lock.yaml`:
- Document 1 contains the pnpm toolchain: `packageManagerDependencies`, `pnpm@12.3.4`, and eight `@pnpm/exe.*` platform packages.
- Document 2 contains the existing **456 application/development dependency package entries**, byte-for-byte identical to the original lockfile. **Zero application dependency version or integrity changes.**
- Both documents declare `lockfileVersion: '9.0'`.
- A single-document YAML parser rejects this file; a consumer reading only the first document sees no ordinary root dependency fields.
This is the same structural shape described in [pnpm/pnpm#13805](https://github.com/pnpm/pnpm/issues/13805) (closed upstream) and **[dependabot/dependabot-core#15904](https://github.com/dependabot/dependabot-core/issues/15904) (still open at assessment time)**. The reports describe GitHub's dependency graph treating that shape as an empty project graph and closing vulnerability alerts without dependency updates.
**Verification boundary:** the 12.3.4 file shape and single-document parser failure were reproduced locally in this repository. The GitHub zero-dependency/alert-closure behavior is an **upstream report**, not a live observation on `coder/ai-sdk`; no upgrade was pushed to test it. Until the downstream parser deployment is verified, preserving security visibility outweighs a toolchain-only upgrade.
This is **not** a pnpm 11 lockfile-readability blocker: native **11.25.0** successfully ran `install --frozen-lockfile --ignore-scripts` against the migrated file in a scratch copy with its package-manager pin set to 11.25.0. With the 12.3.4 pin left in place, the old executable correctly self-switches to 12.3.4 instead.
### Reproduce the local shape
On an isolated checkout of the assessed revision:
1. Change both the mise pnpm pin and root `packageManager` to 12.3.4; install the pinned tool and refresh `mise.lock`.
2. Remove the checkout's generated `node_modules` trees; run `CI=true mise exec -- pnpm install` with Node 26.8.1.
3. Diff `pnpm-lock.yaml`: only the environment document is added. The original dependency document is unchanged.
4. After install, run:
```sh
node <<'NODE'
const fs = require('node:fs');
const yaml = require('./packages/sandbox/node_modules/yaml');
const raw = fs.readFileSync('pnpm-lock.yaml', 'utf8');
const docs = yaml.parseAllDocuments(raw).map(d => d.toJSON());
console.log(docs.length); // 2
console.log(Object.keys(docs[0].importers['.']));
// ['configDependencies', 'packageManagerDependencies']
console.log(Object.keys(docs[1].packages).length); // 456 at the assessed revision
try { yaml.parse(raw); } catch (error) { console.error(error.message); }
// Source contains multiple documents; please use YAML.parseAllDocuments()
NODE
```
## What already works (verified locally)
| Lane | Results under pnpm 12.3.4 |
| --- | --- |
| Node **26.8.1** | Clean install; `pnpm dedupe --check`; `pnpm check`; `pnpm -r build`; `pnpm -r test`; `pnpm publint`; `pnpm attw`: **all pass** |
| Node **22.23.2** | `pnpm -r test`: **all pass** |
| Node **24.20.0** | `pnpm -r test`: **all pass** |
Each test lane: **597 tests in 30 files** (agent 326, sandbox 191, provider 22, effect 45, release tooling 13).
### Packaging and publication checks
- `pnpm publish --dry-run --no-git-checks --access public --json` succeeded for **all three published packages** under both 11.25.0 and 12.3.4. No registry publication was performed.
- File lists, entry counts, and unpacked sizes matched. Compressed sizes and archive hashes differed; packing identical built inputs in scratch fixtures confirmed **byte-identical extracted file contents for all three packages**.
- A marker-based fixture confirmed the same seven publish lifecycle hooks under both versions, including `prepublishOnly`, with and without `--json`. pnpm 12 suppresses hook chatter in JSON output; the initial lack of chatter was **not** skipped validation.
- The existing `--provenance`, public access, and no-git-check flags are accepted. **Actual GitHub OIDC token exchange/attestation/publication was not exercised**; a dry-run is not proof of that live path.
- Live deployment e2e was **not run**, because this is a deferral rather than an adoption PR. It remains mandatory on retry.
Migration assessment: settings, commands, and release cadence
Reviewed the [12.0.0–12.3.4 release series](https://github.com/pnpm/pnpm/releases/tag/v12.0.0), current [settings reference](https://pnpm.io/settings), [publish documentation](https://pnpm.io/cli/publish), exact package metadata, and the local trial.
- **Settings validation:** 12.0 rejects unrecognized workspace keys when the running version satisfies the project pin. All existing repository keys are accepted; no workspace policy edits were needed.
- **Release age:** this repository does **not** explicitly set `minimumReleaseAge`; its built-in default is 1440 minutes, non-strict (already the default since v11). 12.3.0 makes strictness default to true when a cutoff is explicitly configured. The repository's `minimumReleaseAgeExclude` entries were preserved without edits. Future global/CI cutoff overrides must account for that strictness change.
- **Build approvals:** `allowBuilds` remains the explicit allow/deny map. The esbuild postinstall ran; `msgpackr-extract: false` stayed denied. The older `onlyBuiltDependencies` family was removed in v11, not a new migration for this repository.
- **Overrides:** esbuild and single-copy Zod overrides remained unchanged, as did every application lockfile entry. No peer-range narrowing or dependency updates were needed.
- **Resolution/linking/peers:** documented defaults remain `resolutionMode: highest`, `nodeLinker: isolated`, `strictPeerDependencies: false`, `shamefullyHoist: false`. 12.0 changes canonical cycle/peer resolution and Linux `packageImportMethod: auto` to prefer hardlinks; frozen resolution and all type/build/test gates passed here.
- **Workspace orchestration:** 12.1 schedules recursive tasks as dependencies finish rather than waiting for a whole topological chunk; task cycles now error. The repository's recursive gates passed without configuration changes.
- **Package-manager pin:** existing `packageManager` self-switching works. pnpm 12 adds the environment lockfile document for that pin. `pmOnFail` is the supported policy setting; the old `managePackageManagerVersions` setting was removed in v11. No Corepack installation change is needed for mise-based CI.
- **Node:** npm metadata for `pnpm@12.3.4` declares `engines.node: >=18.*`; the mise native distribution and this repository's scripts were exercised on the three lanes above. No Node-floor bump is needed.
- **Freshness commands:** `pnpm -r outdated --json` returned the same dependency names and per-dependency field keys on both versions, exiting 1 when updates existed; JSON key order changed. `pnpm dedupe --check` exited 0 and did not alter the application graph. Do not parse human log ordering as a stable interface.
- **CI/release workflows:** no workflow edits were required for local gates or dry-runs. Retrying adoption must still verify the live OIDC path and consider cache separation; the store path here remained `store/v11`.
- **Stability:** nine stable releases from 12.0.0 (Aug 26) through 12.3.4 (Sep 4), including fixes to CLI flags, store handling, and dedupe convergence. npm's `latest` tag **is already 12.3.4**, confirmed during this assessment. Rapid patching is context, not the sole reason for deferral, and no already-fixed regression is claimed to remain broken.
## Options considered
- **Adopt unchanged:** local gates pass, but leaves the scanner compatibility concern unresolved.
- **Set `pmOnFail: ignore`:** the workaround in the upstream report avoids persisting the package-manager environment document, but [also skips the package-manager version check and automatic download/switch](https://pnpm.io/settings/cli#pmonfail). Not selected: weakening the current contributor pin behavior solely to bypass a scanner gap is a separate policy change, not a transparent pin upgrade.
- **Hand-strip the first document:** not durable; normal pnpm 12 operations can recreate it.
## Re-check triggers and acceptance criteria
Reassess when **any** of these becomes available:
1. `dependabot/dependabot-core#15904` is fixed **and GitHub's deployed dependency graph/alerting is verified to read the application document**, not merely an upstream PR merge.
2. pnpm ships a supported lockfile representation that existing scanners can consume (for example, separated environment data), with no loss of dependency or package-manager integrity.
3. A supported single-document mode preserves the repository's package-manager enforcement/self-switch contract, without `pmOnFail: ignore`.
If none lands, perform a **scheduled status re-check on 2026-09-20** (two weeks); elapsed time alone does not waive the scanner concern.
**Retry procedure:** start from current `origin/main` (including effect's published-pin refresh), update only the pnpm pins/lock metadata, require zero application dependency-version changes, rerun the full Node 26 gates and Node 22/24 tests, compare publish file bytes/lifecycle behavior, and verify scanner coverage on a safe representative repository. Then run **7/7 live agent e2e** against `https://dogfood.cdr.dev` (`dev.coder.com` is retired) using a temporary revoked-after-use token, verify release/OIDC behavior, and open one PR with CI `Required` plus both normal and security Codex review loops clean on its current head.
## Dogfood evidence
Recorded local commands show the two documents, unchanged application graph, and single-document parser rejection. This is **not** a recording of GitHub's dependency graph.

https://github.com/user-attachments/assets/b71a8706-affc-4d53-a323-e0d3523d2e20
---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `coder:openai/gpt-6-astra` • Thinking: `high`_
Contributor guide
Assessment
This issue has not been assessed yet.