Moves all release workflows off of a GH PAT and uses an app with a
short-lived token instead.
Test Plan:
Dry run
[here](https://github.com/vercel/next.js/actions/runs/24934593874).
However, this workflow is blocked until we figure out commit signing for
the bot app. Some options:
- The bot account generates a signing key and we use it in CI (not
great, bypasses the app)
- The org bypasses signature verification for the bot user (also not
great, requires an exemption rule)
- We need to rework the commit step so Lerna does not do the push, and
instead trigger it via the app + GH API. This seems like the best
option, will be added in a follow-up PR.
Note: `create-release-branch` workflow is broken in its current form, as
we will not be restoring administrator privileges to adjust environment
settings. This will become a manual step in the future.
(cherry picked from commit e69de2ffaf)
Switches from a long-lived token to trusted publishing OIDC flow. This requires a bump to Node (for feature support). Otherwise just dropping unnecessary envs.
(cherry picked from commit 129896c2e4)
@wbinnssmith added these scripts a long time ago at my suggestion/urging, but I think they ended up bad for a couple reasons that were hard to foresee at the time:
- afsctool operates in-place and doesn't atomically write the compressed file, so if the process gets interrupted, your `target` directory is corrupted. https://github.com/Dr-Emann/applesauce is better for this reason.
- This doesn't acquire the cargo lock, and modifying files while cargo is running is a good way to get corruption of the `target` directory.
https://github.com/bgw/cargo-apfs-compress is my latest attempt at solving this, though I don't have a `LaunchAgents` config for it.
Regarding `node_modules`: pnpm creates reflinks from a shared global store on apfs. Trying to compress these reflinked files is just going to hurt you because it'll break the data deduplication that would've otherwise happened.
We've had a longstanding issue where you'd have to run `pnpm i` _twice_ on a fresh checkout because `pnpm` wouldn't set up binary symlinks until after the binary exists, but the binary wouldn't exist until after `pnpm build` was done:
https://github.com/vercel/next.js/blob/canary/.github/workflows/build_reusable.yml#L303-L311
This solves that problem using a pnpm preinstall script that creates stub binaries for these files if they don't exist yet (because we haven't run `pnpm build`).
## Test Plan
```
git clean -fxd
pnpm i
pnpm build
pnpm test-start test/e2e/app-dir/next-image/
pnpm test-start-turbo test/e2e/app-dir/next-image/
```
I tested this both locally and inside of a fresh ubuntu VM.
Running `pnpm next` after `pnpm i` but before `pnpm build` gives a helpful error message:
<img width="946" height="125" alt="Screenshot 2026-03-06 at 9 37 14 PM" src="https://github.com/user-attachments/assets/4517e8bd-60ca-44f2-a34c-f7f3ec56c634" />
### What?
Adds `@next/swc-linux-x64-gnu` to the preview tarball patching script
(`scripts/patch-preview-tarball.mjs`):
1. Adds the package to the `PACKAGES_TO_PATCH` list so its tarball URL
is fetched.
2. Adds the package as an explicit dependency in the patched
`package.json`, in addition to the overrides/resolutions.
### Why?
The `@next/swc-linux-x64-gnu` native binary package is an optional
dependency of `next` and may not be installed automatically depending on
the platform/package manager. When testing preview tarballs (e.g., in CI
or on Linux environments), the SWC binary needs to be explicitly present
for Next.js to function correctly. Without this change, preview tarball
testing on Linux x64 could fail because the native binary isn't resolved
from the preview build.
Adding it as a direct dependency (not just an override/resolution)
ensures the package manager actually installs it, since optional
dependencies from overridden tarballs may not be auto-resolved.
### How?
- Added `'@next/swc-linux-x64-gnu'` to the `PACKAGES_TO_PATCH` array so
the script fetches its tarball URL.
- After writing overrides/resolutions, the script now also adds
`@next/swc-linux-x64-gnu` as an explicit entry in `pkg.dependencies`
pointing to the tarball URL.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
`getFailedJobs()` in `scripts/pr-status.js` used jq's `select(.conclusion == "failure")` filter during GitHub API pagination. When a page had 100 jobs but none were failures, jq returned empty output, and the `if (!output.trim()) break` check terminated the pagination loop early — never fetching page 2+ where actual failures existed.
For example, on PR #90755 (129 total jobs), all 100 jobs on page 1 were success/skipped, while the 2 failures (`test unit windows (22) / build` and `thank you, next`) were on page 2 (jobs 101-129). The script reported "Found 0 failed jobs" when there were actually 2.
## Fix
`getFailedJobs()` now delegates to `getAllJobs()` (which fetches all jobs without jq filtering, so pagination works correctly) and filters for failures in JavaScript afterward. This is simpler and avoids the class of bug where jq pre-filtering interacts badly with pagination.
## Test Plan
- Ran `node scripts/pr-status.js 90755` before fix: reported 0 failed jobs
- Ran `node scripts/pr-status.js 90755` after fix: correctly reported 2 failed jobs (`test unit windows (22) / build` and `thank you, next`)
### What?
Adds a new script `scripts/patch-preview-tarball.mjs` that patches a
target project's `package.json` to use Next.js preview tarballs from
`vercel-packages.vercel.app`.
### Why?
When testing unreleased Next.js changes against external projects (e.g.,
reproducing bugs, verifying fixes), there's no convenient way to point a
project at a specific Next.js commit's build artifacts. This script
automates the process of wiring up preview tarball URLs into a project's
dependency overrides.
### How?
The script:
1. **Resolves a commit SHA** via one of three methods:
- `--commit <sha>` — use an explicit commit
- `--branch <name>` — look up the branch tip via the GitHub API (`gh`)
- *(default)* — use the local `git rev-parse HEAD`
2. **Builds tarball URLs** for `next`, `@next/mdx`, `@next/env`, and
`@next/bundle-analyzer` using the
`vercel-packages.vercel.app/next/commits/<sha>/<pkg>` pattern.
3. **Verifies** that the preview tarball exists (HEAD request to the
`next` tarball URL).
4. **Patches `package.json`** in the target project (or its workspace
root) by setting both `overrides` (npm/pnpm) and `resolutions` (yarn)
for the packages.
**Usage:**
```bash
node scripts/patch-preview-tarball.mjs --project <path> [--commit <sha> | --branch <name>]
```
Preview builds served via `vercel-packages.vercel.app` were showing the
published canary version instead of the PR-specific preview version.
This happened because `create-preview-tarballs.js` updated
`package.json` after the build, but the version was already baked into
the compiled `dist/` files by `setNextVersion` in `taskfile-swc.js`.
This extracts the version computation from `create-preview-tarballs.js`
into a new `set-preview-version.js` script that runs before `pnpm run
build`, so the compiled output naturally includes the correct version.
`create-preview-tarballs.js` now reads the version from
`packages/next/package.json` instead of computing it independently.
## Test Plan
1. Create a new app: `pnpm create next-app@canary`
2. Install the preview build from this PR: `pnpm i
https://vercel-packages.vercel.app/next/commits/06681c21e886a97e39d277808780884151c00318/next`
3. Run `pnpm dev` and inspect `next.version` in DevTools — should show
`16.2.0-preview-06681c21-20260306` instead of the canary version
### What?
Prefixes replies posted by `scripts/pr-status.js` with the `🤖` (🤖) emoji.
### Why?
When the pr-status script replies to PR review threads, there's no visual indicator that the reply was generated by AI rather than a human. Adding the robot emoji makes it immediately clear.
### How?
One-line change in the `replyToThread()` function in `scripts/pr-status.js` that prepends `🤖 ` to the reply body before posting it via the GitHub GraphQL API.
### What?
Adds a lint script (`scripts/check-unused-turbo-tasks.mjs`) that detects
unused `#[turbo_tasks::function]`, `#[turbo_tasks::value]`, and
`#[turbo_tasks::value_trait]` definitions across the Rust codebase, and
removes all currently unused items.
### Why?
Dead turbo-tasks definitions add compilation overhead (each generates
trait impls, registrations, etc.) and make the codebase harder to
navigate. A lint check prevents regressions.
### How?
**New script** (`scripts/check-unused-turbo-tasks.mjs`):
1. Discovers all `.rs` files in `turbopack/crates/` and `crates/`
(excluding `turbo-tasks-macros-tests`)
2. Parses three kinds of turbo-tasks annotations:
- `#[turbo_tasks::function]` → extracts `fn` name + context (free,
inherent impl, trait impl, trait default)
- `#[turbo_tasks::value]` → extracts `struct`/`enum` name
- `#[turbo_tasks::value_trait]` → extracts `trait` name
3. Scans all files for identifier references, excluding definition sites
4. Reports any name that has no external references
The script is added to the `pnpm lint` command (runs in parallel with
other checks).
**Removed 23 unused items** across 18 files:
| Kind | Count | Examples |
|------|-------|---------|
| `value` (struct/enum) | 9 | `OutputAssetsWithAvailability`,
`ModuleNameMap`, `OptionModuleIds`, `LockedVersions`,
`AssetContentSnapshot` |
| `function` | 14 | `client_env`, `next_js_file`, `convert_path`,
`is_hot_module_replacement_enabled`,
`any_content_changed_of_output_asset` |
Also cleaned up imports that became unused after the removals.
## Summary
- Add a `--wait` flag to `scripts/pr-status.js` that writes a partial
report with currently available CI results, then blocks on `gh run
watch` until CI completes, and re-runs the full analysis to produce the
final report
- Refactor `main()` into a reusable `runAnalysis()` function so it can
be called twice (partial + final)
- Update the `/pr-status` command to run the script with `--wait` in the
background, present the partial report immediately while CI is still
running, then poll for completion with 5-minute timeouts
### How it works
1. `/pr-status` launches `node scripts/pr-status.js --wait` in the
background (1-minute Bash timeout)
2. The script writes the initial report (with whatever jobs have
completed so far) and prints `Output written to ...`
3. The command polls for that message using `TaskOutput` with 20-second
timeouts, then reads `index.md` and analyzes the partial results
4. Meanwhile, the script blocks on `gh run watch` until CI finishes,
then re-runs the full analysis
5. After presenting the partial analysis, the command polls for the
background script to complete using `TaskOutput` with 5-minute timeouts
(repeating if needed)
6. When it finishes, the command re-reads the final report and analyzes
any newly failed jobs
### Files changed
- `scripts/pr-status.js` — Extracted `runAnalysis()` from `main()`,
added `--wait` flag with `gh run watch` blocking and re-analysis
- `.claude/commands/pr-status.md` — Updated workflow to use `--wait`
with background execution, poll for readiness (20s timeouts), and poll
for final results (5-min timeouts)
## Test plan
- [ ] Run `node scripts/pr-status.js <completed-pr-number>` (no
`--wait`) — should behave identically to before
- [ ] Run `node scripts/pr-status.js <completed-pr-number> --wait` —
should produce report and exit immediately (no waiting needed since CI
is done)
- [ ] Run `node scripts/pr-status.js --wait` on a branch with
in-progress CI — should write partial report, block on `gh run watch`,
then write final report
- [ ] Run `/pr-status` command — should show partial results, then
update with final results after CI completes
---------
Co-authored-by: Claude <noreply@anthropic.com>
### What?
Adds two new subcommands to `scripts/pr-status.js`:
- `reply-thread <threadNodeId> <body>` — posts a reply to a PR review
thread
- `resolve-thread <threadNodeId>` — marks a review thread as resolved
Each generated `thread-N.md` file now includes a `## Commands` section
at the bottom with ready-to-use commands pre-populated with the correct
GraphQL node IDs. The resolve command is only shown for threads that are
still open.
Also updates the pr-status-triage skill to remind agents to reply to
review threads with a description of actions taken before resolving
them.
### Why?
When an agent addresses review feedback, it should close the loop by
replying to the thread (describing what was done) and resolving it.
Previously there was no way to do this from the pr-status workflow — the
agent would need to manually construct `gh api graphql` calls.
### How?
- Fetches GraphQL node IDs for review threads (added `id` to the
thread-level query)
- Uses `execFileSync` with argument arrays (not shell strings) to safely
pass the reply body to `gh api graphql` without shell escaping issues
- GraphQL mutations used: `addPullRequestReviewThreadReply` and
`resolveReviewThread`
- Subcommand dispatch at the top of `main()` checks `process.argv[2]`
against known subcommand names before falling through to the existing
PR-number behavior
## Summary
Fixes `spawnSync /bin/sh ENOBUFS` errors in `scripts/pr-status.js` when
fetching large CI job logs.
The script uses `execSync` (which internally uses `spawnSync`) to fetch
CI job logs via `gh api .../jobs/{id}/logs`. `execSync` has a hard
`maxBuffer` limit (set to 50MB), and when CI job logs exceed that size,
Node.js throws an `ENOBUFS` error. This causes the flaky test detection
to silently fail, reporting 0 flaky tests.
**Before:**
```
Fetching logs for 37 failed jobs...
[stderr] Command failed: gh api "repos/vercel/next.js/actions/jobs/65135029618/logs"
spawnSync /bin/sh ENOBUFS
[stderr] Command failed: gh api "repos/vercel/next.js/actions/jobs/65135029722/logs"
spawnSync /bin/sh ENOBUFS
Found 0 flaky tests (failing on 2+ different branches)
```
**After:**
```
Fetching logs for 18 failed jobs...
Found 0 flaky tests (failing on 2+ different branches)
```
## Changes
- Added an `execAsync()` helper that uses `child_process.spawn` instead
of `execSync`. `spawn` streams data through pipes with no built-in
buffer limit, avoiding `ENOBUFS` entirely.
- Updated `getJobLogs()` and the log-fetching loop in `getFlakyTests()`
to use `execAsync` instead of `exec`.
- All other API calls (small JSON responses) continue using the
synchronous `exec()` helper since they are well within the 50MB limit.
## Test Plan
Ran `node scripts/pr-status.js 90617` against a PR with multiple failed
jobs — completes successfully with no ENOBUFS errors.
## What?
Adds a `maybe-build-native.mjs` script to `packages/next-swc/` that conditionally rebuilds native SWC bindings during `pnpm build`. The script is integrated into the turborepo pipeline as the `build` task for `@next/swc`.
## Why?
Developers often forget to run `pnpm swc-build-native` after pulling Rust changes, leading to confusing runtime errors. Conversely, rebuilding native binaries when nothing changed wastes significant time. This automates the decision so `pnpm build` always does the right thing.
## How?
The `maybe-build-native.mjs` script runs as `@next/swc`'s `build` task and:
1. **Skips in CI** — exits immediately when the `CI` env var is set, since CI uses prebuilt `@next/swc-*` npm packages
2. **Finds the last version bump** — uses `git log -G` to locate the commit that last changed `"version":` in `packages/next/package.json`
3. **Checks for Rust changes** — compares `*.rs` files against the working tree (committed + staged + unstaged) since that version bump commit
4. **Rebuilds if needed** — runs `build-native` and copies/formats the generated TypeScript definitions
5. **Clears stale binaries** — if no Rust changes are detected, removes any leftover `.node` files from `native/` so the prebuilt npm packages are used instead
Turborepo's `turbo.json` is configured with Rust-specific inputs (`crates/`, `Cargo.*`, `rust-toolchain`) and `CI` in `env` so CI and local builds get separate cache keys.
### Files changed
- `packages/next-swc/maybe-build-native.mjs` — new script with the conditional build logic
- `packages/next-swc/package.json` — added `"build": "node maybe-build-native.mjs"`
- `packages/next-swc/turbo.json` — added `build` task with Rust inputs, `CI` env, and `native/*.node` outputs
Fork the set of deployment tests so they run with both turbopack and
webpack. Now that turbopack is the default bundler we need this
coverage.
Because we are now running twice as many tests, decrease concurrency,
otherwise we hit rate limits on vercel. There were attempts to improve
rate limit recovery in the vercel CLI
(https://github.com/vercel/vercel/pull/14443 and
https://github.com/vercel/vercel/pull/14407) which helped but did not
solve the issue. Also there was an investigation into the API service
where we discovered some suspicious but ultimately correct code
(https://github.com/vercel/api/pull/55967).
The basic issue is that the vercel CLI polls `api-deployments-get`
fairly aggressively at the beginning of a deploymnet, and since we start
so many deployments in parallel we will always hit the rate limits. The
recovery logic in the CLI is good but has (reasonably) a fixed set of
retries it will attempt, inevitably some tasks get unlucky. So retrying
the test does work, but really we should slow down which is what we do
here. 😥https://github.com/vercel/next.js/actions/runs/20290161194/job/58272593085
Fixes PACK-5613
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
## Summary
- **Agent skills**: 9 new skill files in `.agents/skills/` covering DCE,
flags, react-vendoring, runtime-debug, PR triage, and skill authoring
- **PR status tooling**: `scripts/pr-status.js` script and
`.claude/commands/pr-status.md` command
- **AGENTS.md**: updated with skill references and development
guidelines
## Test plan
- [ ] No runtime behavior changes
- [ ] Docs and tooling only
---------
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
- Add retry logic (3 attempts with 5s delay) for transient network
failures
- Don't fail the job when cache restoration fails - let workflow build
from source
- Add error handling for dry run failures and JSON parsing
This attempts to address transient failures seen here
https://github.com/vercel/next.js/actions/runs/21735816168/job/62700523424?pr=89574
Disclaimer: This PR is entirely claude+opencode generated.
- Crate name said `next-swc-napi`, which was confusing, because this has a lot more than swc bindings nowdays.
- The crate name didn't match the directory name, which often confused agents.
This does not change the npm package name, as that would likely have a much larger blast radius, and could break more things, to a point where it may not be worth it.
This updates to run the entire E2E deploy tests same as we do currently
but with adapters enabled to catch regressions there. These run on a
separate test team to isolate from existing test team and avoid rate
limits.
Test run of the workflow done here
https://github.com/vercel/next.js/actions/runs/21418745794
Improve performance and token usage of `/ci-failures` command
include type in failure table, provide more instructions
use subagents
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the PR.
- Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to understand the PR)
- When linking to a Slack thread, you might want to share details of the conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
`type: 'string'` conflicts with `choices: T[]`. It's basically `string |
T` which just collapses to `string` if `T` is a union of string
literals.
Before:
```console
$ p pack-next --help
...
--compress ... [string] [choices: "none", "strip"]
```
After:
```console
$ p pack-next --help
...
--compress ... [choices: "none", "strip"]
The file already had the `@ts-check` directive, so the IDE did show errors in that file. But since it was not included in the root `tsconfig.json`, those errors were not reported during CI runs.
One issue found this way was that the `related` flag was defined but never used. The referenced script was removed in #67644.
## Summary
Comprehensive improvements to the PR stats action for more reliable benchmarks and reduced CI noise.
### Vercel KV Integration
- Add `@vercel/kv` for historical data persistence
- Track metrics over time with `loadHistory()` / `saveToHistory()`
- Display trend sparklines in comments when history is available
### Comment Generation Refactor
- New `METRIC_LABELS` and `METRIC_GROUPS` configuration system
- Organized metrics by category (Dev Server, Production Builds, Production Runtime)
- Better formatting utilities (`prettifyTime`, `formatChange`, `generateTrendBar`)
- Bundle group totals for KV persistence
### Noise Reduction
- Significance thresholds: `50ms AND 10%` for time, `1KB AND 1%` for size
- Additional `<2%` filter for long-running operations (builds)
- Filters typical CI variance while catching real regressions
### Stats Config Updates
- Add `measureDevBoot: true` to enable dev boot benchmarks
- Fix `appDevCommand` to include `dev` subcommand
- Add `--webpack` flag to `appBuildCommand`
- Rename "Client Bundles (main, webpack)" to "Client Bundles (main)"
- Add `turbopack: {}` to test configs
### GitHub Actions Updates
- New `action.yml` with bundler input parameter
- Workflow improvements in `pull_request_stats.yml`
### New Files
- `scripts/test-stats-benchmark.sh` - Local testing script
- `.github/actions/next-stats-action/src/util/stats.js` - Shared stats utilities
- `.github/actions/next-stats-action/test-local.js` - Local comment testing
## Test Plan
```bash
cd .github/actions/next-stats-action
node test-local.js # Basic test
node test-local.js --with-history # Test with trend sparklines
```
## Summary
Development scripts for profiling and benchmarking Next.js dev server boot time.
### Benchmarking Scripts
- `benchmark-boot-time.sh` - Wall-clock benchmarking with two metrics:
- **Listen time**: When TCP port accepts connections
- **Ready time**: When first HTTP request succeeds
- `benchmark-next-dev-boot.js` - Multi-iteration benchmarking with statistics (median, p95, stddev)
### Profiling Scripts
- `profile-next-dev-boot.js` - CPU profiling infrastructure using V8 inspector
- `analyze-profile.js` - Analyze CPU profiles to identify hot modules by CPU time
### Analysis Scripts
- `analyze-dev-server-bundle.js` - Bundle analyzer for dev server (generates treemap report)
- `trace-cli-startup.js` - Module loading trace to identify slow imports
## Usage
```bash
# Benchmark dev server boot time (5 runs by default)
./scripts/benchmark-boot-time.sh
# Multi-iteration benchmark with stats
node scripts/benchmark-next-dev-boot.js --iterations 10
# Generate CPU profile
node scripts/profile-next-dev-boot.js
# Analyze profile output
node scripts/analyze-profile.js .next/cpu-profiles/*.cpuprofile
# Analyze dev server bundle
node scripts/analyze-dev-server-bundle.js --open
```
## Test Plan
- [x] Run `./scripts/benchmark-boot-time.sh` locally
- [x] Verify scripts execute without errors
The `scripts/install-native.mjs` script is run in the postinstall step
of the Next.js monorepo to install the published binary packages of the
latest Next.js canary version. This change makes the script respect the
`NEXT_TEST_PREFER_OFFLINE` environment variable, allowing it to prefer
installing previously downloaded packages from the local cache. This is
useful for scenarios where network access is limited or unavailable.
This PR extracts the existing git change detection logic from the `get-changed-tests` script into a separate module. This allows reusing the same logic in the `run-for-change` script, ensuring consistent behavior across the two scripts to determine changed files. Specifically, this ensures that in stacked PRs, the docs-only change detection (and the test-only change detection in the upstack PR) works correctly by diffing against the appropriate base branch.
I don't know what this script does, but it compiles with the workspace's version of reqwest, and that reduces the copies of reqwest we pull in.
Might slightly speed up cold builds of the entire workspace.
Libraries that want to support Next.js without specific public adaptors
must use dynamic requires and other non-standard tricks to conditionally
load Next.js specific behavior. To simplify this we are adding a
condition to Next.js so that package authors can load Next.js specific
versions of code publicly (through export maps) and privately (through
import maps).
The condition is added when cacheComponents is enabled. This will allow
libraries to target modern Next.js conditionally without publishing new
majors with a new floor version for optional peer dependencies on
Next.js. In the future this will be the only mode for Next.js and the
condition will always be present
The condition is currently only implemented for bundled code. We want to
support this for externals too but it is tricky because determining the
right condition requires running Node.js first and I want to be careful
about adding any process indirection that slows down cold start. This
will be addressed in a future update.
---------
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>