100 Commits
Author SHA1 Message Date
Benjamin WoodruffandGitHub f6d094061e [ci] Background some steps in build_reusable to reduce setup overhead, remove unneeded lld installation (#96195)
This is a pretty minor win, but it can save a few seconds per job.


https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/

Also remove the installation of `lld` for rustc with `apt-get`. It's
very slow, and rustc should already ship with `lld` these days? The
composite action to install the rust toolchain isn't run often (test
shards download a prebuilt native binary), but `build-native` it can
block other jobs from running and become a bottleneck.

Testing the full `build-and-deploy` job, which uses the rust install
action: https://github.com/vercel/next.js/actions/runs/30138483769
2026-07-28 22:49:20 -07:00
Benjamin WoodruffandGitHub 606b8e88bb Turbopack: Only extend the watcher's batch window for unfiltered events (#96186)
If we get metadata events, or if we (on Linux with inotify) get file
access events, these aren't useful, and we filter them, but we were
unintentionally extending the deadline for the batch because of them.
2026-07-27 15:07:04 -07:00
Benjamin WoodruffandGitHub 59160c612d Turbopack: Store watcher batch state with bitflags instead of 4 different sets (#96114)
Previously we we had 5 total `FxHashSet`s in `BatchedInvalidations`. Now
we've got one `FxHashMap` and one `FxHashSet`. There was potential
overlap in paths represented in all of these sets.

The old implementation was suboptimal for two reasons:
- We ended up cloning and storing `PathBuf` more times. (Note: there's a
good future opportunity for an arena here too)
- If a path was in both `path` and `path_and_children`, we'd later try
to invalidate paths associated with that twice. This will be a no-op as
we have a lock on the invalidator map and the duplicated path has
already removed from the invalidator map, but it makes us do extra map
lookups that aren't needed.

This still keeps around a separate set for `new_paths` so that we can
more efficiently iterate over them. It's possible it could benefit from
being a flag in the same set, but it's less obvious.
2026-07-27 15:07:03 -07:00
Benjamin WoodruffandGitHub 82934fc83f Turbopack: Very minor improvements for watcher loop (#96103)
Noticed these things while reading through the code.
2026-07-23 16:18:01 -07:00
Benjamin WoodruffandGitHub 00c9e3aa25 Turbopack: Refactor watcher event handling and batching logic (#96087)
This change should be a no-op, I just moved some of the logic for
handling event processing that didn't have any control flow implications
out of the nested loop. The level of nesting here was making the code a
little hard to read.
2026-07-23 16:18:01 -07:00
Benjamin WoodruffandGitHub 89c9487766 Turbopack: Split up turbo-tasks-fs/src/lib.rs into smaller modules (#96030)
This file was way too huge and was wasting a ton of tokens when trying
to use it with agents.

`turbo-tasks-fs/src/disk.rs` is still a little bigger than I'd like, but
I think this is an overall improvement.
2026-07-22 16:38:52 -07:00
Benjamin WoodruffandGitHub 8882653a6f Turbopack: Use Arc<PathMap> and Box<Path> to make InvalidatorMap slightly more efficient (#95987)
Addresses
https://github.com/vercel/next.js/pull/95951/changes#r3617631291

- We often already have an `Arc<PathBuf>` here.
- The data stored in the queue is temporary, so the slight extra size of
`Arc<PathBuf>` is fine, and saving the clone is nice.
- The other references to the `Arc<PathBuf>` are not long-lived, so
convert it into a `Box<Path>` once it goes into the locked map.
2026-07-22 16:38:52 -07:00
Benjamin WoodruffandGitHub 5639c4ecff Turbopack: Use swc_core::ecma::utils::prop_name_eq for a couple of the next-custom-transforms (#96035)
Found this helper while looking through one of @sampoder's PRs, noticed
that these places could benefit from it.
2026-07-22 16:38:16 -07:00
Benjamin WoodruffandGitHub e1c8cc7206 Turbopack: Remove chunk group id from value of chunk groups map (#95142)
It looks like we were redundantly storing these group ids in the
`FxIndexMap`, when we could just use it's entry position.
2026-07-21 22:20:01 +00:00
Benjamin WoodruffandGitHub e630d81822 [ci] Enforce minimumReleaseAge in e2e tests that install external dependencies (#95628)
We also mitigate the security impact of e2e tests by running everything
in VMs, and by not reading caches in release jobs, but we should still
be using `minimumReleaseAge` in e2e tests.

Ideally we'd also use lockfiles here too, and I'll work on that next,
but that is likely a lot more complicated because we probably need some
way to merge lockfiles, since we can't pin the next.js and react
versions in every test, and we need a reasonable/efficient way to
bulk-update all the lockfiles for tests that need it.

Depends on https://github.com/vercel/next.js/pull/95668 for a bunch of
windows path representation fixes that this ends up exposing in CI,
because adding workspace files causes pnpm to change the absolute path
representation used in the Windows junction points that it creates.
2026-07-21 14:54:01 -07:00
Benjamin WoodruffandGitHub 3214020b2d Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows (#95668)
I was seeing failures on https://github.com/vercel/next.js/pull/95628
and went down this rabbit hole.

- We were (incorrectly) not canonicalizing the `DiskFileSystem` root
dir. This happened to work out for us because pnpm also wasn't
canonicalizing junction point targets, and so the root dir prefix
matched. However after `#95628`, pnpm started canonicalizing junction
point targets because we added `pnpm-workspace.yaml` files. This was
just coincidence and indicative of much deeper problems.

- `dunce` isn't a good fit for us. It picks a win32 or verbatim
representation based on path length, which would mean that long paths
inside of a short root path would never work because we'd never be able
to strip the prefix, since we'd be looking for a win32 path prefix.
Omnipath is better for what we want to do:
https://docs.rs/omnipath/latest/omnipath/windows/trait.WinPathExt.html

- The windows verbatim path format works with long paths (>260
characters) and behaves a lot closer to cross-platform unix paths, which
is what we want. E.g. if somebody tries to import a JS module from a
Windows 8.3 short path, it should fail. This will fail with verbatim
paths (good!). This PR *always* uses the verbatim path format internally
within `DiskFileSystem`.

- `read_link` was re-inventing `try_from_sys_path` but badly. Just call
`try_from_sys_path`.

- `turbopack/crates/turbo-tasks-fs/src/embed/file.rs` was dead code.

- `validate_path_length_inner` was incorrectly referring to verbatim
paths as UNC paths. These are two different things.

- Canonicalize any absolute symlink targets outside of the
`DiskFileSystem` root path, resolving any symlinks they may have,
normalizing case-insensitive paths, and handling Windows 8.3 short name
format.

## Some Context About Windows Path Formats:

- win32 paths: These are your normal user-friendly `C:\foo\blah` paths.
When calling most filesystem APIs, these are limited to 260 characters
unless the user has enabled long paths on the system, and avoiding the
limit with these paths also requires a [`longPathAware` application
manifest](https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#longPathAware).
You can mix `/` and `\` in win32 paths, and Windows will normalize the
path separator for you.

- verbatim paths: This is a rust-specific name (used by the stdlib) for
the "extended length" prefixed paths. These are paths starting with
`\\?\`. These paths can be up to ~32,767 characters long. They do not
support Windows 8.3 shortnames. They do not normalize between `/` and
`\`, and do not support `.` or `..` components, however
[`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join)
will do this normalization if given a verbatim path.

- Windows 8.3 shortnames: A legacy format where paths can be represented
by 8 characters, followed by a tilde and a digit (or hash in some
extreme cases where there are many collisions), followed by a 3
character file extension. NTFS supports this,
[ReFS](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview)
(used by [Windows Dev
Drive](https://learn.microsoft.com/en-us/windows/dev-drive/)) does not.

- UNC ("Universal Naming Convention"): This is a specific representation
of paths used for remote files on network servers.

- `file://` URIs: These must be formed from [win32 style
paths](https://docs.rs/url/latest/url/struct.Url.html#method.from_file_path),
though the normal 260 character limit typically does not apply here.

- canonicalize: Corresponds to
[`std::fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).
This involves actual fs operations (that we can't easily track), and
recursively resolves symlinks. It will fail if the file path does not
exist. On windows, this always returns a verbatim path.

- wide format: Windows paths are utf-16. Generally we don't support
paths that can't be encoded to utf-8, but we should try to fail as
obviously as we can in these cases. We don't have good filesystem issue
handling yet though.

- case sensitivity: Generally paths are not case-sensitive on Windows or
macOS, but are case-sensitive on most Linux filesystems. The exact
definition of how case sensitivity works with unicode paths is highly
platform and filesystem dependent.

- junction points: The windows equivalent of symlinks (symlinks are also
supported, but require developer mode to be turned on). These can
contain an arbitrary path reference in any format (win32, verbatim, etc)
with any case and with windows 8.3 short names. Junction points are
always target absolute paths to directories.
2026-07-21 14:54:01 -07:00
Benjamin WoodruffandGitHub 2a3f8e0cd9 Turbopack: Use Arc<PathBuf> for keys in turbo-task-fs's per-path MutexMap (#95951)
We clone these `PathBuf`s a bunch of times, so use `Arc`s
2026-07-21 12:52:55 -07:00
Benjamin WoodruffandGitHub 390eff3b86 [ci] Share a single browser instance across all test suites in a single job (#95589)
The idea here is that we waste a bunch of CPU setting up each test suite
by spawning an entirely new Chrome process, but playwright can share the
same browser across many tests. Tests are still isolated at the browser
level, just not at the OS process level.

Hoping to see some performance improvement in e2e tests. Compare to:
https://github.com/vercel/next.js/pull/95617

Somewhat unscientific (single sample) results:

```
Comparing the head-commit build-and-test runs — PR #95589 run [28981702219 ](https://github.com/vercel/next.js/actions/runs/28981702219)vs PR #95617 run [28981686692](https://github.com/vercel/next.js/actions/runs/28981686692). Both ran 101 jobs and both succeeded.

  ┌─────────────────────────┬────────────────┬───────────────────────┬───────────────────┐
  │         Metric          │ #95617 control │ #95589 shared browser │       Delta       │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Raw wall-time sum       │ 616.2 min      │ 571.2 min             │ −45.0 min (−7.3%) │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Billable (ceil per job) │ 658 min        │ 617 min               │ −41 min (−6.2%)   │
  └─────────────────────────┴────────────────┴───────────────────────┴───────────────────┘

  The shared-browser experiment is cheaper, and the savings land almost exactly where you'd expect — the browser-driven test suites — while non-browser jobs (rust, lint, unit, windows) are flat within noise:

  ┌─────────────────────────────────┬─────────┬────────┬───────┐
  │          Job category           │ control │ shared │ delta │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test prod                       │   130.7 │  110.5 │ −20.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components dev            │    51.5 │   42.9 │  −8.7 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack dev                   │    75.1 │   69.5 │  −5.5 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack production            │    86.3 │   81.1 │  −5.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test dev                        │   110.4 │  105.5 │  −4.9 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components prod           │    48.7 │   49.0 │  +0.3 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ firefox and safari              │     5.3 │    7.3 │  +2.0 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ flake-detection jobs (combined) │     ~15 │    ~21 │   +~6 │
  └─────────────────────────────────┴─────────┴────────┴───────┘

  Takeaways:
  - Net savings of ~41 billable minutes per run (~6%), concentrated in the Chromium-driven test prod/dev, turbopack, and cache components suites — consistent with reusing one browser process instead of spawning per-suite.
  - The small regressions are in firefox and safari and the "new/changed tests for flakes" jobs (+~8 min combined). Worth a glance, though they may just be run-to-run variance.

  Caveat: this is a single run per PR, so there's real variance run-to-run. That said, the fact that the deltas track the browser-heavy jobs specifically — and not the Rust/lint/unit jobs — is a good signal the effect is genuine rather than noise. If you want a firmer number, re-running each PR 2–3× and averaging would tighten it up.
```
2026-07-21 01:36:10 +00:00
Benjamin WoodruffandGitHub 5fe8c690eb Turbopack: Simplify parent directory creation retry loop logic (#95835)
Noticed while reading through the symlink code.

The symlink logic had some clever `ErrorKind::AlreadyExists` logic that
folded the retry into the main retry loop, but we still had a separate
retry loop for after handling `ErrorKind::NotFound`. This folds the
loops together.

I hand-implemented the symlink version of this and had opus port it to
the non-symlink version.
2026-07-20 14:10:25 -07:00
Benjamin WoodruffandGitHub 0cfc85ceae Clarify AI-assisted contribution policy in PR template and AGENTS.md (#95629)
## Summary

Documents the policy around AI-assisted contributions, for both humans
and agents:

- `.github/pull_request_template.md`: notes that AI use is encouraged
for researching, creating, and reviewing changes, but contributors must
deeply understand their contributions, and PR descriptions from external
contributors must be written by a human.
- `.github/ISSUE_TEMPLATE/1.bug_report.yml`: adds the equivalent note
for bug reports — issue descriptions from external contributors must be
written by a human, though AI may help create reproductions.
- `AGENTS.md`: replaces the old "PR Descriptions" section with two new
sections. "GitHub Pull Requests" distinguishes branch PRs (agents may
write descriptions) from fork PRs targeting `vercel/next.js` (agents may
not). "GitHub Issues, Comments, and Discussions" restricts agent-written
issues, discussions, and comments to members of the
`vercel`/`vercel-labs` GitHub orgs, tells agents how to check membership
via the GitHub API, lists what agents can still help non-members with
(drafting details, reviewing, reproductions, translation, searching for
duplicates), and carves out exceptions (commenting on the user's own PR,
Vercel-operated bots, GitHub/Graphite review bots, and forks of the
repo).
- Renames the LLM watermark marker from `NEXT_JS_LLM_PR` to
`NEXT_JS_LLM` everywhere it appears (`AGENTS.md`,
`contributing/repository/pull-request-descriptions.md`, and the
`create-pr`/`backport-pr` skills), since it now also applies to issues,
discussions, and comments. Nothing in the repo consumes the old marker
name.
- `.agents/skills/create-pr/SKILL.md`: adds a "Fork PRs vs Branch PRs"
section referencing the `AGENTS.md` policy. Assorted typo fixes along
the way.

## Verification

- `gh api /user/memberships/orgs` and `gh api
orgs/vercel/members/<login>` (confirmed the documented membership checks
work and return the documented statuses)
- Not run: no test/build commands (docs-only change; prettier applied
via the pre-commit hook)

<!-- NEXT_JS_LLM -->
2026-07-10 11:27:52 -07:00
Benjamin WoodruffandGitHub 9286844641 [ci] Split up large instant-validation tests (#95627)
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
2026-07-08 19:56:28 -07:00
Benjamin WoodruffandGitHub a87692e17c [ci] Split up large rsc-build-errors development test (#95624)
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
2026-07-08 19:56:27 -07:00
Benjamin WoodruffandGitHub 4df37b6eed [ci] split up large cache-components-errors tests (#95623)
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
2026-07-08 19:56:27 -07:00
Benjamin WoodruffandGitHub 337a40c672 [ci] Pin typescript version in tests (#95619)
Typescript 7 came out, which is breaking a lot of our e2e tests.
2026-07-08 14:12:48 -07:00
Benjamin WoodruffandGitHub d23d069ebe [ci] Split up large create-next-app/templates/matrix test (#95555)
Same motivation as #95553, but another test suite.

This is a bit messier than the other PR because there's not as clear of
a split point, so it uses sharding.
2026-07-07 17:11:46 -07:00
Benjamin WoodruffandGitHub c3dd28fd6a [ci] Split up large cache-components-dev-warmup test suite (#95553)
This splits two of our slowest test suites into smaller pieces that can
be run in parallel and retried in isolation.

## Context

Jest treats test suites as the smallest test execution unit.

Slow test suites can lead to situations where a test shard is using very
little CPU waiting for one last test suite to finish, and when tests
fail and have to be retried, we have to retry the entire suite.

Here's a particularly bad example:

<img width="3116" height="2634" alt="Screenshot 2026-06-08 at 20-00-05
CI Telemetry — turbopack production tests (1)"
src="https://github.com/user-attachments/assets/b5534200-4b77-40d5-a219-4b6fc552ed77"
/>

I instructed Claude to fetch test timing information from GitHub Actions
Artifact, and to report the slowest test suites.

> How timings work today
>
> - The `fetch-test-timings` job runs `node run-tests.js --timings
--write-timings`, which pulls per-file durations from a Vercel KV store
and uploads them as the test-timings artifact (test-timings.json, 5-day
retention).
> - Each sharded job downloads that artifact and run-tests.js -g N/M
greedily bin-packs whole test files into groups by predicted duration
(run-tests.js:528-549).
> - I pulled the artifact from the July 7 canary run (28843797348): 1684
test files, ~61,500s total, median 20s, but max 1023s.
>
> Since the file is the unit of scheduling, a shard can never finish
faster than its largest file. The job durations from that run confirm
the imbalance this causes: test dev 4/10 took 28 min while siblings took
10–17; test cache components dev 6/6 took 13 min vs 5–6 for siblings;
test turbopack production 5/7 took 20 min vs 10–11.

> The slowest suites

```
┌────────────────────┬────────────────────────────────────────────────────────────────────────────┐
│        Time        │                                   Suite                                    │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 1023s + 933s       │ test/development/app-dir/cache-components-dev-warmup/ (both variants)      │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 942s               │ test/production/create-next-app/templates/matrix.test.ts                   │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 788s + 432s + 232s │ test/e2e/app-dir/cache-components-errors/cache-components-errors.*.test.ts │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 779s               │ test/development/acceptance-app/rsc-build-errors.test.ts                   │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 470s               │ test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts               │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 457s               │ test/e2e/app-dir/actions/app-action-node-middleware.test.ts                │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 451s               │ test/e2e/app-dir/next-after-app/index.test.ts                              │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 447s               │ test/e2e/app-dir/css-order/css-order.test.ts                               │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 426s               │ test/e2e/telemetry/config.test.ts                                          │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 402s               │ test/production/debug-build-path/debug-build-paths.test.ts                 │
└────────────────────┴────────────────────────────────────────────────────────────────────────────┘
```

> Reading these files, almost every one is slow for the same structural
reason: a `describe.each(...)` over fixtures/modes/runtimes where each
iteration boots its own `nextTestSetup` (dev server or full production
build), run serially within one Jest worker:

> - **cache-components-dev-warmup:** `describe.each` over 2 fixture
dirs, and it restarts the dev server (stop/clean/start) before every one
of ~17 tests. It's slow by design (cache-warmup isolation), so per-test
cost can't shrink — but the file can be split.
> - **create-next-app matrix:** `describe.each(app|pages)` × `it.each`
over a ~16–48-combination flag matrix, each doing a full CNA scaffold (+
dev-server cycle for pages), fully serial.
> - cache-components-errors.test.ts: 7490 lines, 129 tests, with
per-describe `next build --experimental-build-mode compile` and per-path
generate builds in prod mode.
> - **css-order:** three sibling `describe.each` blocks over 4–6
chunking modes, each mode booting its own server, ~3 blocks × modes ×
page-pair orderings.
> - **next-after-app, opentelemetry:** `describe.each` over
runtimes/configs, again one server per iteration.
2026-07-07 14:51:23 -07:00
Benjamin WoodruffandGitHub bd77087864 [ci] Migrate webpack tests back to arm runners (#95549)
I partially rolled back CI jobs because we ran out of runners. IT
increased the concurrency from 50 to 100, so I've been slowly migrating
back to arm runners while watching that we don't end up with long queue
times again.

Thread: https://vercel.slack.com/archives/C0B0LKK5QD7/p1782838752523649

Average job queue time in the last day has been 59 seconds:

https://github.com/vercel/next.js/actions/metrics/performance?dateRangeType=DATE_RANGE_TYPE_CUSTOM&range=1783296000000-1783296000000
2026-07-07 07:50:14 -07:00
Benjamin WoodruffandGitHub 727a3ffa86 [ci] Skip apt-installed dependencies in playwright install step (#95535)
The hypothesis is that this is just installing some font stuff that
isn't actually needed for Chromium to work:

```
The following additional packages will be installed:
  libnss3-tools xfonts-encodings xfonts-utils
Recommended packages:
  fonts-ipafont-mincho fonts-liberation-sans-narrow fonts-tlwg-loma
The following NEW packages will be installed:
  fonts-freefont-ttf fonts-ipafont-gothic fonts-liberation fonts-tlwg-loma-otf
  fonts-unifont fonts-wqy-zenhei xfonts-cyrillic xfonts-encodings
  xfonts-scalable xfonts-utils
The following packages will be upgraded:
  libnss3 libnss3-tools
2 upgraded, 10 newly installed, 0 to remove and 35 not upgraded.
```

`apt` is very slow, so skipping this step should significantly reduce
setup time per test shard.

It seems like this isn't always true for other browsers, so only skip
this for Chromium (that's fine, that's what most of our jobs use
anyways).

On 16 core arm runners, "Install Playwright Browsers" typically took
18-24 seconds. Now it takes 5-6s.

Let's estimate that saves about 15s per Linux job that installs
Chromium. Claude estimates that there are 73 chromium-only Linux jobs on
this PR:

<img width="891" height="148" alt="Screenshot 2026-07-06 at 10 30 16 PM"
src="https://github.com/user-attachments/assets/725893fa-9f6f-4806-a3b4-9858ab0ebe66"
/>

So that's 15*73/60=18.25 minutes saved per PR push (the average PR gets
many pushes). At the public (before any volume discounts) price of
$0.026/minute for the 16 core arm runners, that comes out to $0.4745 per
PR push saved.

This workflow has had 3,894 runs in the last 30 days, so that could be
$1,847/mo saved, but many of those workflow runs were canceled, and some
likely failed before getting to these jobs, so the actual savings are
probably somewhere around half of that -- scroll through
https://github.com/vercel/next.js/actions/workflows/.github/workflows/build_and_test.yml
and see that a little less than half of the workflow runs end up
canceled.
2026-07-07 09:48:46 +02:00
Benjamin WoodruffandGitHub 6f592dec9d [ci] Disable mid-stack PR optimization for native PR stacks (#95427)
https://vercel.slack.com/archives/C04KC8A53T7/p1783018076576349

<img width="1015" height="347" alt="Screenshot 2026-07-02 at 11 57
05 AM"
src="https://github.com/user-attachments/assets/74d99079-d165-435a-aece-161862795ef6"
/>

If we're in the middle of a stack, but the PRs below us in the stack are
already merged, we should not skip CI jobs. This logic isn't smart
enough to handle that.
2026-07-02 19:28:37 +00:00
Benjamin WoodruffandGitHub 24262a8212 [ci] Actually migrate Turbopack jobs back to ARM (#95386)
This is embarrassing, I wasn't paying enough attention when I put up
#95374. We have to both change the build dependency *and* switch the
machine type...
2026-07-02 08:40:01 -07:00
Benjamin WoodruffandGitHub a26a1d640b [ci] Avoid running full CI mid-stack for GH-native stacks, same as we do for Graphite (#95218)
Running CI only on the top and bottom of the stack avoids expensive
`O(n^2)` behavior when merging one PR at a time and recursively rebasing
all the others starting at the bottom of the stack.

GitHub added this to the webhook/GH actions object for us to be able to
do this: https://vercel.slack.com/archives/C01SKER9S1G/p1777926655162839

Prior to this PR being part of a stack, fails open (runs full CI):
<img width="1938" height="993" alt="Screenshot 2026-06-26 at 3 03 37 PM"
src="https://github.com/user-attachments/assets/54d6055a-2860-45b3-a03a-a9356b3db2a4"
/>

Example mid-stack run (skips the full e2e jobs):
<img width="1917" height="664" alt="Screenshot 2026-06-26 at 3 24 33 PM"
src="https://github.com/user-attachments/assets/9ee466da-f3ea-4298-b5b8-2527d197c121"
/>

https://github.com/vercel/next.js/actions/runs/28268549361/job/83760907003?pr=95219
2026-07-01 23:22:24 -07:00
Benjamin WoodruffandGitHub 36331cfe3b [ci] Migrate Turbopack build_and_test jobs back to arm64, leave webpack jobs as x86-64 (#95374)
IT increased the concurrency limit from 50 to 100. This ramps back up
the `build_and_test` job. I'll keep an eye out for long queue times
before migrating us all the way back to arm64.


https://vercel.slack.com/archives/C0B0LKK5QD7/p1782910263667649?thread_ts=1782838752.523649&cid=C0B0LKK5QD7
2026-07-01 21:47:32 +00:00
Benjamin WoodruffandGitHub 792f746339 [ci] Move some test suites back to x86-64 runners to reduce pressure on our currently-limited pool of arm runners (#95332)
https://vercel.slack.com/archives/C0B0LKK5QD7/p1782838752523649

This is a mitigation until we can get IT to increase the concurrency on
`ubuntu-latest-16-core-arm-oss` runners.
2026-07-01 02:41:34 +00:00
Benjamin WoodruffandGitHub 38bd101b10 [ci] Migrate build_and_test and pull_request_stats to use arm64 runners (#94870)
A 16 core x86-64 runner is $0.042/minute. A 16 core arm64 runner is
$0.026/minute.
https://docs.github.com/en/billing/reference/actions-runner-pricing

It also looks like they're faster! After factoring the performance
improvement, this appears to be a ~50% cost reduction for migrated jobs.

Datadog SQL:

```
SELECT
  REGEXP_REPLACE(ci_job_name, '\s*\([0-9]+/[0-9]+\)', '') AS ci_job_name,
  labels,
  branch,
  commit_date,
  SUM(duration) / 1000 / 1000 / 1000 / 60 as minutes,
  ci_pipeline_id
FROM dd.ci_pipelines(
    columns => ARRAY ['@git.commit.committer.date', '@ci.pipeline.id', '@duration', '@git.branch', '@ci.node.labels', '@ci.job.name'],
    event_type => 'job',
    filter => '@git.repository.id_v2:github.com/vercel/next.js @ci.pipeline.name:build-and-test @ci.provider.instance:github-actions @ci.pipeline.id:(28266192824-1 OR 28266190122-2) [email protected]:*changed*',
    from_timestamp => now() - INTERVAL '30 days',
    to_timestamp => now()
  ) AS (
    commit_date VARCHAR,
    ci_pipeline_id VARCHAR,
    duration BIGINT,
    branch VARCHAR,
    labels VARCHAR,
    ci_job_name VARCHAR
  )
GROUP BY
  REGEXP_REPLACE(ci_job_name, '\s*\([0-9]+/[0-9]+\)', ''),
  labels,
  commit_date,
  branch, 
  ci_pipeline_id
ORDER BY 
  REGEXP_REPLACE(ci_job_name, '\s*\([0-9]+/[0-9]+\)', ''),
  branch,
  commit_date,
  minutes;
```


https://app.datadoghq.com/ddsql/editor?ddsql_dataset_id=34de66b9-30f4-48dd-ac98-354dc7e67fdf

Full Data:
https://docs.google.com/spreadsheets/d/1SszR3RsPeJ13nVW5ggMakwy87fkQMqWMHbZDehHH8IA/edit?usp=sharing

Here's the abbreviated data for jobs longer than 5 minutes:
<img width="1187" height="656" alt="Screenshot 2026-06-26 at 4 38 00 PM"
src="https://github.com/user-attachments/assets/9341039e-c4ea-405c-8d24-cdbc5ed1ba24"
/>

It seems like for the jobs switched over to arm64, they run in about
~73-82% of the time as arm.
2026-06-30 09:57:35 -07:00
Benjamin WoodruffandGitHub f84c69476a Turbopack: This bool could've been an enum (#95288)
<img width="588" height="141" alt="Screenshot 2026-06-29 at 4 39 36 PM"
src="https://github.com/user-attachments/assets/a8b0981e-fa76-433b-845a-989b840e0ac1"
/>

https://x.com/charliermarsh/status/2069796668506042584
2026-06-30 03:32:56 +00:00
Benjamin WoodruffandGitHub c518402e07 [ci] update relay-compiler to 21.0.1 (#95217)
We need to upgrade this because the ancient version of relay-compiler
that we were using didn't support arm64.
2026-06-30 01:27:24 +00:00
Benjamin WoodruffandGitHub f96ff7c739 Turbopack: Delete dead AttachedFileSystem type (#94960)
Git blame:
https://github.com/vercel/next.js/commit/4e9482d015dc794e0420791c42ab11136ab24695
2026-06-23 11:43:52 -07:00
Benjamin WoodruffandGitHub 4638ab18fb Turbopack: Avoid clone when sorting by module file path (#95079)
Minor nit: In the worst-case (module id can be an `RcStr` or a u64),
this eliminates two `RcStr` clones+drops per iteration of the sort
algorithm.

The code is cleaner with `sort_by_key`, but `sort_by_key` introduces an
artificial lifetime issue that `sort_by` avoids.

This is a long-standing and widely documented language issue:
https://github.com/rust-lang/rust/issues/34162
2026-06-23 07:58:51 +02:00
Benjamin WoodruffandGitHub c06894aea9 Turbopack: Fix log box test on react 18 (#95078) 2026-06-23 05:38:20 +00:00
Benjamin WoodruffandGitHub 7cb54ace75 [ci] Update playwright to 1.61.0 (#94871)
[Playwright v1.61.0 adds support for
ubuntu-26.04](https://github.com/microsoft/playwright/releases/tag/v1.61.0).
When Lindsey created our arm64 runners (see
https://github.com/vercel/next.js/pull/94870), [he picked the
ubuntu-26.04
image](https://vercel.slack.com/archives/C01LN7C5QR5/p1781608258610389?thread_ts=1781118174.353839&cid=C01LN7C5QR5)
(which is technically still [in
preview](https://github.com/actions/runner-images#available-images), but
IMO that's fine).
2026-06-18 21:39:11 +00:00
Benjamin WoodruffandGitHub 51d440520a [ci] Only install chromium-headless-shell and not chromium (#94882)
Because it's not sure which one you'll want, playwright installs TWO
copies of Chromium by default. A full copy and a stripped down headless
version.

Most of the install time is spent in `apt-get`/`dpkg`, but this at least
buys us a few seconds back.

Before:

![Screenshot 2026-06-16 at
6.02.44 PM.png](https://app.graphite.com/user-attachments/assets/1cb05dca-c36d-472d-8ffd-1e177f77fc10.png)

After:

<img width="1700" height="552" alt="Screenshot 2026-06-16 at 6 24 50 PM"
src="https://github.com/user-attachments/assets/d382cb8e-a875-4d67-92d6-32ff51c06488"
/>


Playwright also installs ffmpeg in case you want to perform a screen
recording (we don't), but it's much smaller (2.3 MiB), and there doesn't
appear to be a way to disable it.
2026-06-17 21:20:01 +00:00
Benjamin WoodruffandGitHub eb9b1dad10 [ci] Remove playwright stamp file logic (#94876)
This is left over from self-hosted runners. This should never be used in
gh hosted runners.
2026-06-17 09:08:41 -07:00
Benjamin WoodruffandGitHub b08fb8d922 [ci] Clean up references to self-hosted runners (#94827)
We're no longer using self-hosted runners. I had claude look for places
where we had references to self-hosted runners and I had it clean them
up.

Stats job for this PR:
https://github.com/vercel/next.js/actions/runs/27572281422/job/81511924690?pr=94827
2026-06-16 09:17:10 -07:00
Benjamin WoodruffandGitHub 36a8f4c271 [ci] Use node-version-file when we already have a repository checkout and reduce hardcoded references to node versions (#94780)
I noticed the `node-version-file` option for `setup-node` while
reviewing https://github.com/vercel/nextjs-react-compiler/pull/2

We should use it in places where we're already checking out the
repository anyways. It should make it easier to keep node versions in
sync.
2026-06-16 00:48:22 +00:00
Benjamin WoodruffandGitHub b8d268d0da [ci] Download fnm directly from GitHub releases and validate checksum (#94579)
I determined that [`nvm` wasn't sufficient for our use-case since we
need to support
windows](https://github.com/vercel/next.js/pull/94175#issuecomment-4654622674),
so this doubles down on `fnm`.

This reduces risks of supply chain attacks with `fnm`.

This also cleans up some old stuff that was left over from self-hosted
runners and/or that was there for debugging, if it isn't useful anymore.
2026-06-10 14:23:29 -07:00
Benjamin WoodruffandGitHub 3caee03f2a [ci] Reduce usage of turborepo cache in build_and_deploy.yml (#94319)
We're consolidating on `actions/cache` now that we got rid of
self-hosted runners. This doesn't remove all of the uses of turborepo's
cache (notably sccache), but this does eliminate the remote turborepo
cache for the native build cache and the docker image.

These build steps use docker so that we're building against an old
pinned glibc (2.31), and so that we can have access to both `aarch64`
and `x86_64` musl sysroots.

## Testing

**Local Build:** `node scripts/docker-native-build.js` still works (this
script just exists for being able to debug these builds locally)

**CI Build:**
- Run an automated-preview CI job, but cancel it before the native build
succeeds to test writing to the docker cache but not the native cache:
https://github.com/vercel/next.js/actions/runs/26986955366/job/79638633136?pr=94319
- Re-run the job with only the docker image cache, see that we get a
cache hit for the docker image:
https://github.com/vercel/next.js/actions/runs/26986955366/job/79639365126?pr=94319
- Re-run the job again and see that we hit the native build cache hit
and don't touch docker at all:
https://github.com/vercel/next.js/actions/runs/26986955366/job/79647324952?pr=94319
- Manually trigger the job to do a full preview build:
https://github.com/vercel/next.js/actions/runs/26989861549
2026-06-10 14:23:18 -07:00
Benjamin WoodruffandGitHub fc8edc4722 [ci] Remove dead playwright docker image creation logic (#94576)
We reverted the use of this docker image in
https://github.com/vercel/next.js/pull/94366

We're going to use GitHub actions custom runner images instead (which
don't use docker)
2026-06-09 10:15:47 -07:00
Benjamin WoodruffandGitHub e8638a6c07 Revert "[ci] Increase TEST_CONCURRENCY from 8 to 12 (#94138)" (#94517)
There's some evidence that this was meaningfully increasing flakiness:
https://vercel.slack.com/archives/C04KC8A53T7/p1780773226831399
2026-06-06 21:41:53 +00:00
Benjamin WoodruffandGitHub ef63481d68 [ci] Set persist-credentials: false for all GH actions (#94214)
The actual security implications of this are likely *very* minor, but in
the interest of reducing how many credentials we have, there's little
reason for us not to set this to `false`.

In cases where we create a new commit via GH actions, we already do that
via the GitHub API, so those don't need the on-disk git credentials to
push.

---

From: https://github.com/actions/checkout :

> The auth token is persisted in the local git config. This enables your
scripts to run authenticated git commands. The token is removed during
post-job cleanup. Set persist-credentials: false to opt-out.

```
    # Whether to configure the token or SSH key with the local git config
    # Default: true
    persist-credentials: ''
```
2026-06-05 18:28:18 +00:00
Benjamin WoodruffandGitHub 2b8f5f59a3 [ci] Switch release-next-rspack to a more explicit environment name (#94480)
Matches the environment name I asked IT to use for NPM trusted
publishing:
https://vercel.slack.com/archives/C01LN7C5QR5/p1780600987032909

Nothing else uses the `npm` environment, but the name is confusing. We
want to use separate environments for these publishing workflows as they
allow us to define things like reviewers, if we want to block the
release on a manual review.
2026-06-05 11:19:30 -07:00
Benjamin WoodruffandGitHub 7147d0e036 Revert "ci: run Linux Playwright jobs in prebuilt Microsoft container (#93794)" (#94366)
We're going to go with custom images instead, though we don't have an
implementation for this yet.

This does save a meaningful amount of minutes, but it also significantly
increases complexity.

This is a clean revert, minus the fix that the PR included for listening
on localhost in `packages/next/src/build/analyze/index.ts`, which is
still good.
2026-06-05 17:21:21 +00:00
Benjamin WoodruffandGitHub 8bc7074612 Turbopack: Perform issue filtering without turbo-task functions (#93885)
The filtering logic is generally very cheap, many issues are
short-lived, and filtering is unlikely to trigger more than 1-2 turbo
tasks per issue.

Wrapping it in another task per issue doesn't make a ton of sense.
2026-06-05 10:15:48 -07:00
Benjamin WoodruffandGitHub 4c3cdf61de Turbopack: Move turbo-tasks/src/raw_vc.rs to turbo-tasks/src/vc/raw.rs (#94239)
This has slightly annoyed me for a long time.
2026-06-02 23:38:30 +00:00
Benjamin WoodruffandGitHub 0a355571a0 [ci] Update all actions/* dependencies (#94174)
Previously we pinned all of these to their current versions at the time.
That meant that some of these got pinned to really old versions. This
updates all of the "first-party" `actions/*` dependencies.

Prompted claude with:

```
For all of these uses of actions/*, make sure we're pulling in the latest stable version (check the gh cli). When upgrading across major versions, fetch the release notes from the GH releases using the gh CLI and make sure that there are no problems that the upgrade will cause.
```

Claude read all the release notes and seems to think these upgrades are
safe.

![Screenshot 2026-05-27 at
3.36.15 PM.png](https://app.graphite.com/user-attachments/assets/4b863905-a983-43a1-ba3d-5d3cc2988bc2.png)
2026-05-28 09:26:57 -07:00
Benjamin WoodruffandGitHub 3a4c36747c [ci] Use Turbopack in non-webpack-specific e2e test jobs (#94005)
Turbopack is the default bundler, so we should prioritize testing that
in these variants of the CI runs (cache components, node streams,
windows).

There's also some limited evidence that indicates that it might be
faster for e2e test runs, though that's not conclusive (because of
different shard sizes):
https://vercel.slack.com/archives/C0B0LKK5QD7/p1779320615501259
2026-05-28 00:43:59 +02:00
Benjamin WoodruffandGitHub dbc973054f [ci] Rename GRAPHITE_TOKEN to GRAPHITE_CI_OPTIMIZER_TOKEN (#94166)
This secret got renamed when we issued a new one. Discussion:
https://vercel.slack.com/archives/C01LN7C5QR5/p1777662825523989

<img width="670" height="226" alt="Screenshot 2026-05-27 at 12 12 42 PM"
src="https://github.com/user-attachments/assets/65c3d125-a493-4b02-b461-3a5292604ad6"
/>


<img width="446" height="167" alt="Screenshot 2026-05-27 at 12 11 42 PM"
src="https://github.com/user-attachments/assets/24e226fb-5818-45de-9883-e8fd7d0d26c6"
/>
2026-05-27 20:10:08 +00:00
Benjamin WoodruffandGitHub 3127d71efc [ci] Give the build cache in cache in build_and_deploy a more obvious name, not just a hash and run number (#93957)
This should make the output of `gh cache list` a lot clearer. Without
this, it's not obvious where these cache keys are coming from.
2026-05-27 11:36:32 -07:00
Benjamin WoodruffandGitHub 6b64722383 [ci] Increase TEST_CONCURRENCY from 8 to 12 (#94138)
We're using 16 core machines, our `TEST_CONCURRENCY` of 8 is arbitrary.
This tries increasing that to 12.

- GH Actions for 8 (control, #94142):
https://github.com/vercel/next.js/actions/workflows/build_and_test.yml?query=branch%3Abgw%2Ftest-concurrency-8
- GH Actions for 12 (this branch):
https://github.com/vercel/next.js/actions/workflows/build_and_test.yml?query=branch%3Abgw%2Ftest-concurrency-12
- GH Actions for 16 (#94133):
https://github.com/vercel/next.js/actions/workflows/build_and_test.yml?query=branch%3Abgw%2Ftest-concurrency-16

It appeared that (across a couple of runs) that `TEST_CONCURRENCY=16`
*may* lead to more test failures. Plausibly this could be due to
timeouts. `TEST_CONCURRENCY=12` does not appear to be causing noticeably
more failures than usual, and seems to be slightly speeding things up.

Datadog SQL:

```
SELECT
  SUM(duration) / 1000 / 1000 / 1000 / 60 as minutes,
  branch,
  ci_pipeline_id
FROM dd.ci_pipelines(
    columns => ARRAY ['@ci.pipeline.id', '@duration', '@git.branch'],
    event_type => 'step',
    filter => '@git.repository.id_v2:github.com/vercel/next.js @ci.pipeline.name:build-and-test @ci.provider.instance:github-actions @ci.pipeline.id:(26473602379-1 OR 26484229016-1 OR 26485947297-1 OR 26493064901-1 OR 26492002693-1 OR 26485986756-1 OR 26485929595-1 OR 26484214883-1 OR 26480156826-1)',
    from_timestamp => now() - INTERVAL '30 days',
    to_timestamp => now()
  ) AS (
    ci_pipeline_id VARCHAR,
    duration BIGINT,
    branch VARCHAR
  )
GROUP BY branch, ci_pipeline_id ORDER BY branch;
```

<img width="1193" height="807" alt="Screenshot 2026-05-26 at 11 07
05 PM"
src="https://github.com/user-attachments/assets/bbd75ee1-9766-48a8-90da-6225908de251"
/>
2026-05-27 18:17:40 +00:00
Benjamin WoodruffandGitHub 67d5f9615a [ci] Explicitly disable package-manager-cache for uses of actions/setup-node (#93953)
If this action detects that you're likely using `npm` it (by default)
enables a cache for `npm`. We use `pnpm`, so it doesn't do anything (and
we manually set up a pnpm cache in some places), but the implicit
behavior here is a little dangerous: e.g.
https://adnanthekhan.com/posts/angular-compromise-through-dev-infra/#finding-the-pivot

So let's explicitly disable this everywhere. It wasn't doing anything
for us anyways.
2026-05-27 06:05:07 +00:00
Benjamin WoodruffandGitHub 552908ef33 [ci] Resize tiny jobs in integration_tests_reusable.yml to use smaller machines (#93886)
Claude identified these as clear candidates for shrinking machine size.

Here are the runner types we have that are attributed to the OSS cost
center:

![Screenshot 2026-05-15 at
6.18.18 PM.png](https://app.graphite.com/user-attachments/assets/7f6d927b-1820-4be5-bf8b-3ee8fc5c2988.png)

Ideally we'd have 1 and 2 core machines as well, but I don't really want
to bother IT right now.
2026-05-26 16:16:24 -07:00
Benjamin WoodruffandGitHub b6fea5d080 [ci] Add an AGENTS.md for GitHub Actions (#94056)
Copied from the one I wrote for front:
https://github.com/vercel/front/blob/main/.github/AGENTS.md

I removed the bit about our internal https://github.com/octo-sts like
service that's not accessible by OSS repos yet.
2026-05-26 22:08:38 +00:00
Benjamin WoodruffandGitHub 658222700a [ci] Fix the playwright-chromium Dockerfile (#94064)
Added in https://github.com/vercel/next.js/pull/94051

Failing in canary:
https://github.com/vercel/next.js/actions/runs/26311675098

Successful `workflow_dispatch` job here:
https://github.com/vercel/next.js/actions/runs/26319776612/job/77486403513

Tested locally with

```
podman build . --build-arg=NODE_VERSION=20.9.0 --build-arg=PLAYWRIGHT_VERSION=1.58.2
```

If you're on arm64, you need to change the url from `fnm-linux.zip` to
`fnm-arm64.zip` and update the checksum.
2026-05-26 16:48:34 +00:00
Benjamin WoodruffandGitHub 88350cfee6 [ci] Set TURBO_TASKS_AVAILABLE_PARALLELISM to 4 in most e2e tests (#93996)
By default, turbo-tasks will try to use every available CPU core.

While we do want it to use as many resources as possible if the CPU is
otherwise idle, this may hurt us when the CPU is busy, as it will lead
to more locking, more thread contention, more memory usage, etc.

**This environment variable does not strictly limit Turbopack to using 4
cores, it just restricts the number of main tokio worker threads and
changes how we shard dashmaps.**

We currently are using 16 core CI machines, but I want to experiment
with 32 core build machines with a higher `TEST_CONCURRENCY`, and the
per-core scaling of Turbopack was a concern that came up, as wall time
improvements are sub-linear starting somewhere around 16 cores.


## Test Plan

Watch CI, compare build times in this commit and the parent one.

Attempt with a value of 2: https://github.com/vercel/next.js/pull/94001
Attempt with a value of 8: https://github.com/vercel/next.js/pull/94002

![Screenshot 2026-05-20 at
4.53.38 PM.png](https://app.graphite.com/user-attachments/assets/2e07db4a-068f-4344-863c-995877b254ce.png)

I think this data is too noisy to draw any conclusions.
2026-05-21 21:14:54 +00:00
Benjamin WoodruffandGitHub 80cb3447c1 [ci] Add an TURBO_TASKS_AVAILABLE_PARALLELISM environment variable that overrides std::thread::available_parallelism callsites (#93995)
We want to use this to potentially improve efficiency in e2e tests where
we're running many instances of Turbopack at once. By default,
turbo-tasks will try to use every available CPU core.

**This environment variable does not strictly limit Turbopack to using 4
cores, it just restricts the number of main tokio worker threads and
changes how we shard dashmaps.**

This option will not make sense for the vast majority of users, it only
makes sense when you are running *many* instances of Turbopack at once,
and the exact semantics are potentially confusing, as it is not a hard
limit.

## Test Plan

Build vercel-site with this set to 1 and see a little over 100% CPU
usage. Set it to 8 and see around 800% CPU usage.

```
TURBO_TASKS_AVAILABLE_PARALLELISM=1 pnpm next build --experimental-build-mode=compile
```
2026-05-20 17:14:48 -07:00
Benjamin WoodruffandGitHub 4e6331397d [ci] Also pin first-party GH actions (#93609)
We were already pinning nearly all of our third-party GitHub actions,
but this also pins our first-party ones (things starting with
`actions/*`.

This gets us closer to being able to enforcing pinning at the repository
level:
![Screenshot 2026-05-06 at
4.39.46 PM.png](https://app.graphite.com/user-attachments/assets/ced0b3f2-7de8-4a9a-aa79-8d3efa74197d.png)

The one remaining blocker is this self-reference in a
`pull_request_target` action:
https://github.com/vercel/next.js/blob/c06d94ba22d0156e8bff28c81f7874b73d80ed03/.github/workflows/pull_request_auto_label.yml#L65

I'm still figuring out the best approach to do there, and I'll submit
that in a separate PR.

Discussion here about enforcing this org-wide:
https://vercel.slack.com/archives/C0AM84PRSGL/p1778110550384279
2026-05-08 20:10:55 +00:00
Benjamin WoodruffandGitHub 8a80793335 [ci] Ensure graphite_ci_optimizer fails open even if github fails to fetch the action (#93615)
Thread here:
https://vercel.slack.com/archives/C08V7CU57E1/p1778186281862589?thread_ts=1778182852.380899&cid=C08V7CU57E1

Sometimes GitHub can fail to fetch and run an action due to general
infra flakiness. That can cause this workflow to fail-closed, even
though the action itself is designed to fail-open.

![Screenshot 2026-05-07 at
2.03.36 PM.png](https://app.graphite.com/user-attachments/assets/61b749f8-549c-4945-9384-adae58a7fb1d.png)

Reduce the chance that this can fail-closed due to infra flakiness by
setting `continue-on-error`.
2026-05-07 21:32:05 +00:00
Benjamin WoodruffandGitHub f638777ddb Turbopack: Change DynEffect::dyn_value to DynEffect::value_hash, a simple u128 (#93552)
As usual, `Effect` is way too generic. This code gets a lot simpler if
you can assume that `value` returns a `u128`.
2026-05-07 12:38:00 -07:00
Benjamin WoodruffandGitHub dccb95c3b1 Turbopack: Cache effect errors, replace the write_lock Mutex with a lazily created Notify instance (#93476)
This is a mostly-by-hand refactor of some of the caching logic in
https://github.com/vercel/next.js/pull/92300

- **Cache errors.** We often end up applying effects multiple times in
frequent succession, and many filesystem effects already have their own
backoff/retry loops, which can make them very slow when they fail, so it
seems generally better to cache the failures.
- **Add `EffectLastApplied` enum:** It's a matter of opinion, but I
think the `EffectLastApplied` enum makes it easier to reason about the
different states in the system. `Option` was difficult to reason about,
and it would've been even more confiusing if it was an
`Option<Result<(), Arc<dyn EffectError>>`.
- `tokio::sync::Mutex<()>` is overkill here, `tokio::sync::Notify` is a
simpler (and smaller) primitive that does the core of what we needed the
`Mutex` for. We could also use `EventListener` here, but I didn't.
(`EventListener` is really just smol's version of `Notify`)
- We can lazily allocate the `Notify` instance inside of `last_applied`.
We have to grab this lock anyways to check for the cached fast-path. If
we're not in the fast-path, we can re-use the lock guard to create and
store the `Notify` instance.
- **Perf:** The old code's slow path had to always lock `last_applied`
three times. Now, if there's no concurrent writer (common case), it only
locks twice: Once before application, and once after.
- **Perf:** Use a smallvec when computing unique indices to avoid a
bunch of heap allocations. Unless there's a bug, we don't expect any of
the keys to have more than one value (returns an error).
- **Perf:** Store keys as `Box<[u8]>` instead of `Vec<u8>`, saving 8
bytes per key.

Currently, if `effect.dyn_apply()` panics, we just do our best to mark
the effect as unapplied to avoid a deadlock. This could alternatively
poison the object.

## Memory Usage

Memory usage can increase if we're storing errors (heap allocated,
stored in `Arc`) we previously wouldn't have, but if we assume that
errors are uncommon, this refactor decreases the size of the data
structures involved.

Before this change, eagerly allocating a `tokio::sync::Mutex` was
costing 40 bytes per effect, making the entire `EffectEntryState` struct
64 bytes:

<img
src="https://app.graphite.com/user-attachments/assets/989ac229-9e2f-4370-8e32-b14e10e09248.png"
width=500>
<img
src="https://app.graphite.com/user-attachments/assets/f2e14416-17c3-4961-8c00-fa7a81846fa0.png"
width=500>

After the changes, the entire `EffectLastApplied` enum is 40 bytes

<img
src="https://app.graphite.com/user-attachments/assets/18e4fc0a-e7f8-4fad-b9bc-c22ba3eefb6c.png"
width=500>

After wrapping the `EffectLastApplied` enum in a `parking_lot::Mutex`,
it comes in at 48 bytes (after alignment), 16 bytes smaller than the old
struct.
2026-05-06 23:23:54 +00:00
Benjamin WoodruffandGitHub e68639f83a chore: Prune list of users in labeler json (#92984)
TSIA
2026-04-18 21:38:11 -07:00
Benjamin WoodruffandGitHub f609c4d4fd CI: Remove dead yarn support from next-stats-action (#92587)
I guess next.js used `yarn` a long time ago? We don't need to support this anymore.
2026-04-16 16:21:54 -07:00
Benjamin WoodruffandGitHub 844af6c901 Turbopack: Use an async lock in the filesystem watcher (#92906)
We know that we have a lot of lock contention when creating watchers, because many watchers are often set up at roughly the same time near the start of building a route. Internal discussion thread: https://vercel.slack.com/archives/C09R44U5HQW/p1776271283799709

@lukesandberg's suggestion was that the lock is held over syscalls and file IO, so it's possible that this could starve the Tokio worker threads, so we should use an async lock so that we can yield back the worker thread.

Because this would simply unblock *other* work, and because this codepath is only used in `dev`, there's no sane way to benchmark or measure this, so we'll just have to hope and pray it's an improvement.

This PR is entirely AI-generated.
2026-04-16 12:54:56 -07:00
Benjamin WoodruffandGitHub f381e3e587 Turbopack: Use lld again on ARM64 glibc Linux (#92860)
`lld` is much faster than the default gnu `ld` linker on most Linux
systems.

Timeline:
- https://github.com/vercel/next.js/pull/91477 removed these flags as
part of cleanup because they're now default on x86-64 glibc linux.
However, they are not default on arm64.
- https://github.com/vercel/next.js/pull/91799 removed these flags from
musl, and lost the comment explaining reason for the `gnu-lld-cc`
option.

Without this change, doing a dev build of a trivial change to
`crates/next-napi-bindings/src/lib.rs` can take over a minute.

I tested this by looking at htop near the end of a build and observing
`lld` instead of `ld`.
2026-04-15 23:33:05 +00:00
Benjamin WoodruffandGitHub 8a2c5e017e Turbopack: Use FrozenMap for module export information (#92802) 2026-04-15 10:24:23 +02:00
Benjamin WoodruffandGitHub ddd625d492 chore: Use from_raw_parts for ResolvedVc::deref_vec and ResolvedVc::deref_slice (#92749)
In reality, I expect this to compile to the same code, but using `from_raw_parts` is technically more correct here as there are no guarantees about the memory layout of `Vec<T>` or `[T]` for any two different values of `T`, even if both values of `T` have the same size and alignment.
2026-04-13 16:46:47 -07:00
Benjamin WoodruffandGitHub 631fae951d CI: Download and run self-contained datadog-ci instead of using pnpm dlx or npx (#92546)
`pnpm dlx` isn't using a lockfile. Using the self-contained binary from
GitHub and validate the checksum lets us effectively pin `datadog-ci`
and all of its possible transitive dependencies.

This is a follow-up for
https://vercel.slack.com/archives/C0APPN2LC83/p1775073127894859
2026-04-10 10:12:46 -07:00
Benjamin WoodruffandGitHub 1816450605 CI: Another attempt to fix pnpm versions for next-stats-action on canary (#92550)
It looks like https://github.com/vercel/next.js/pull/92533 didn't fully solve our issue here: https://github.com/vercel/next.js/actions/runs/24162886733/job/70518191406#step:7:163

`corepack use [email protected].0` runs `pnpm install`, but we want to run `pnpm install` with specific flags including `--no-frozen-lockfile`.

What I really want is some way to get corepack to update the `packageManager` field, but not run `pnpm install`.

We already have to modify the `package.json` ourselves to fix `engines.pnpm`, so let's just not call `corepack use`. It seems that the changes to `packageManager` are picked up the next time that `pnpm` is run with no other interaction needed:

![Screenshot 2026-04-08 at 5.03.24 PM.png](https://app.graphite.com/user-attachments/assets/0cc50764-3a9e-4dec-980c-7389e39cef07.png)
2026-04-09 11:26:54 -07:00
Benjamin WoodruffandGitHub 96f759d89b CI: Fix issue with conflicting 'engines.pnpm' field in next-stats-action (#92533)
This job has been failing on canary pushes, but not on PRs. I'm not sure why it's only happening in canary pushes, but hopefully this should fix the specific error we're seeing when it does fail.

Discussion: https://vercel.slack.com/archives/C04KC8A53T7/p1775571376147179
2026-04-08 13:40:13 -07:00
Benjamin WoodruffandGitHub 5abeaea316 CI: Use minimumReleaseAge in pnpm workspace files (#92480)
Depends on pnpm 10.x: https://github.com/vercel/next.js/pull/92283

Enables https://pnpm.io/settings#minimumreleaseage to protect against supply-chain attacks.

The `minimumReleaseAgeExclude` list is copied from https://github.com/vercel/front/blob/1c3a9458d970265e1fb5ec14ddfed9a0eab2abef/pnpm-workspace.yaml#L13, with `react` and `react-dom` added.
2026-04-08 11:35:50 -07:00
Benjamin WoodruffandGitHub ed396cadae CI: Use pnpm lockfile for next-stats-action (#92488)
Previously, we were just copying the `package.json` into the container and `pnpm install`ing it.

This wouldn't respect the lockfile or `minimumReleaseAge`. This uses `pnpm deploy` based on the documentation here: https://pnpm.io/docker
2026-04-08 08:26:17 -07:00
Benjamin WoodruffandGitHub 38cffffecc CI: Remove unused dependencies from next-stats-action dockerfile (#92485)
We don't need nano, htop, and screen inside of a CI dockerfile.
2026-04-07 18:48:34 -07:00
Benjamin WoodruffandGitHub 68d6729f31 CI: Update and cleanup next-stats-action, force using pnpm 10.33.0 (#92475)
This should fix the stats action: We were checking out Next.js 16.2.2, and that doesn't support the `--out` flag for `pnpm pack`. Let's just force pnpm 10.33.0 here for now.

While we're here, I made some other updates and improvements:
- Update dockerfile to use Ubuntu 24.04
- Update dockerfile to use corepack 0.34.6
- Fix an issue where `pnpm install`ing outside of the docker container could break the docker container build (use `--exclude=node_modules`)
- Avoid using a shell with string interpolation (potential escaping issues) for `cd`ing, just set `cwd`.
2026-04-07 14:17:58 -07:00
Benjamin WoodruffandGitHub 8aca9e3bd1 Include more context in macOS Rosetta 2 warning message (#92436)
Addresses this missed review comment: https://github.com/vercel/next.js/pull/92220#discussion_r3024004242

There's only so much context we can provide because there's a ton of different ways users could've installed node incorrectly.
2026-04-06 23:47:38 -07:00
Benjamin WoodruffandGitHub 3d21b79213 Turbopack: Add a check for node version before allowing workerThreads (#91614)
We want to start dogfooding `workerThreads`, but we can only safely do it if you have a new enough node version.

We don't have any sort of strong enforcement around node.js version in front (e.g. you could have a misconfigured nvm or fnm), so we need to check the node version first.

- https://github.com/napi-rs/napi-rs/issues/2555#issuecomment-3645173223
- https://github.com/nodejs/node/pull/55877
- https://github.com/nodejs/node/pull/61400
- https://github.com/nodejs/node/pull/61661

Looks like this:

![Screenshot 2026-03-18 at 1.04.10 PM.png](https://app.graphite.com/user-attachments/assets/bfdb1051-2ebc-403a-af5c-7c715d651aa9.png)

Related Node.js version bump for front: https://github.com/vercel/front/pull/65059
2026-04-06 21:04:42 -07:00
Benjamin WoodruffandGitHub df6cd599b3 Turbopack: Remove turbo_tasks::apply_effects, use Effects::apply instead (#91858)
Having both `apply_effects` and `Effects::apply` is confusing.

`apply_effect`'s API seems a bit bad/dangerous because it combines work that should be done inside of a strongly-consistent operation (collecting the effects and reading their `Vc`s) with work that should be done at the top-level outside of any operation or turbo-task function (applying the effects).

The first commit in this PR removes the `apply_effect` API and tries to improve documentation, including adding a doctest that compiles.

The second commit in this PR is an LLM-driven refactor to remove the `apply_effect` callsites.
2026-04-06 20:53:39 -07:00
Benjamin WoodruffandGitHub 0dc354b8d7 chore: Upgrade to pnpm 10.x (#92283)
This will let us potentially use https://pnpm.io/settings#minimumreleaseage
2026-04-06 18:12:50 -07:00
f6663b75fd Turbopack: Dedupe our two filesystempath extension/extension_ref functions (#92048)
PR is AI-generated

- `extension` and `extension_ref` do the same thing.
- `extension_ref`'s API that returns an `Option` was better than
`extension`'s API that returns a potentially-empty string.
- In some places we were splitting into a stem and extension, and
joining them back to a filename, but those places could just get the
original path's filename.

---------

Co-authored-by: Tobias Koppers <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Benjamin Woodruff <[email protected]>
2026-04-03 21:51:33 -07:00
Benjamin WoodruffandGitHub 3c301acc32 Turbopack: Remove FileSystemPath::value_to_string in favor of ValueToStringRef::to_string_ref (#92284)
`FileSystemPath::value_to_string` was just calling `ValueToString::to_string`.

But:
- It's better to call `ValueToStringRef::to_string_ref`
- We don't really get much value from this inherent method except for avoiding a few imports?

I suspect this is left over from when we used to always wrap `FileSystemPath` in `Vc`.
2026-04-03 19:08:01 -07:00
Benjamin WoodruffandGitHub 124c4000ab docs: Ask AI agents to self-identify themselves in PR descriptions (#92235)
This is inspired by
https://glama.ai/blog/2026-03-19-open-source-has-a-bot-problem#user-content-how-are-these-agents-setup

If agents follow this pattern, we could potentially add labels (this PR
does not touch the labeler) to these PRs which may help with review and
triage. This does not change how we'd deal with these PRs (AI generated
PRs are okay!), but I would like to know if a PR is AI generated when
reviewing it.
2026-04-03 18:55:42 -07:00
Benjamin WoodruffandGitHub 35f26a2fac Restart the next dev server if .next is deleted (#92135)
This PR is AI generated.

If this directory is deleted while the dev server is running, we can't reasonably recover, so we should restart the process.

Addresses https://vercel.slack.com/archives/C03KAR5DCKC/p1774757692640999
2026-04-03 17:20:55 -07:00
93dc872d0d Generate a CLI warning if using Rosetta 2 on Apple Silicon (#92220)
This PR is AI generated.

Test plan (following https://github.com/vercel/next.js/pull/81950):

- Build a preview deployment:
https://github.com/vercel/next.js/actions/runs/23858712091/job/69559813673
- Enable Rosetta
- Download a standalone x64 macos binary from
https://nodejs.org/en/download

```
export PATH=/Users/bgw/Downloads/node-v22.17.1-darwin-x64/bin/:"$PATH"`
pnpm dlx create-next-app@canary --yes intel-app
cd intel-app
nvim package.json # edit the next.js version to point to the preview deployment
pnpm i
pnpm dev
```

<img width="723" height="192" alt="Screenshot 2026-04-01 at 9 25 04 AM"
src="https://github.com/user-attachments/assets/a953ef8f-7fee-4aa0-9ef8-36ef667de734"
/>

<img width="1022" height="278" alt="Screenshot 2026-04-01 at 9 26 35 AM"
src="https://github.com/user-attachments/assets/a0d4b3bd-f8b7-4bbc-a7be-9113dabe189f"
/>

(the bmi2 issue should be fixed by
https://github.com/vercel/next.js/pull/92177, I just hadn't rebased)

---------

Co-authored-by: Benjamin Woodruff <[email protected]>
Co-authored-by: Claude <[email protected]>
2026-04-01 22:25:21 +00:00
04f137e40b Rename SingleModuleReference::asset to SingleModuleReference::module (#92028)
The field holds a Module, not a generic asset. This aligns the naming
with the type it actually stores.

---------

Co-authored-by: Tobias Koppers <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Benjamin Woodruff <[email protected]>
2026-03-28 16:20:06 -07:00
Benjamin WoodruffandGitHub e2af4d8809 Turbopack: Implement TraceRawVcs and NonLocalValue correctly for Effects (#89133)
In subsequent PRs, I want to store a `ResolvedVc` (specifically, a `FileSystemPath`, which contains a `Vc<Box<dyn FileSystem>>`) inside of an `Effect`.

To do this in a way that wouldn't break a hypothetical future tracing garbage collector, we must implement `TraceRawVcs` on `Effect`.

The previous PR changed the `Effect` type from a closure to a trait in order to enable this.
2026-03-24 15:15:35 -07:00
Benjamin WoodruffandGitHub 468a40fa32 Turbopack: Define Effect as a trait instead of a closure (#89080)
This switches effects (special `Collectible`s used to defer file writes) from using a closure representation to using a trait representation.

This makes constructing effects a little more annoying, but it ensures that we'll be able to correctly implement `TraceRawVcs` on these in a subsequent PR.

Changes to `turbo-tasks/src/effect.rs` were done by hand, an LLM was used for updating the two callsites.
2026-03-24 14:34:40 -07:00
Benjamin WoodruffandGitHub 75c51c6541 Turbopack: Use debug = "line-tables-only" for dev builds (#91539)
This defaults to `true`: https://doc.rust-lang.org/cargo/reference/profiles.html#debug

Nobody on the team uses a debugger often enough for this to be worth the time tradeoff: https://kobzol.github.io/rust/rustc/2025/05/20/disable-debuginfo-to-improve-rust-compile-times.html

I'm gathering some rough numbers, and I'll add them to the PR.

After `cargo clean` and one `cargo build -p next-napi-bindings` with each profile (Linux arm64):

```
du -sh target/*
4.0K    target/CACHEDIR.TAG
7.9G    target/debug
14G     target/dev-with-debug
7.5M    target/generated
```

This is nearly half the size.

Due to cargo quirks, the `dev` profile defaults to the `target/debug` directory. Of course, any disk size wins you get are more than offset if you need to use a debugger and end up with an additional profile.

```
hyperfine -w 1 -r 5 -p 'cargo clean' 'cargo build --profile dev-with-debug -p next-napi-bindings' 'cargo build -p next-napi-bindings'
```

...pending...

(this is on https://github.com/bgw/benchmark-scripts/, on my M2 Macbook inside of a Linux VM this takes closer to 2m20s)
2026-03-17 17:20:12 -07:00
Benjamin WoodruffandGitHub 39e705ca9f Turbopack: Merge release-with-assertions-no-lto profile into release-with-assertions (#91530)
- More profiles means more bloat to the `target` directory and more cold builds.
- There's no practical use case where we'd be okay with the performance hit of `debug-assertions` and not okay with the performance hit of `lto = false`. Neither profile was suitable for benchmarking.
- `release-with-assertions-no-lto` is too long to type and was giving me carpal tunnel.
2026-03-17 16:36:59 -07:00
Benjamin WoodruffandGitHub 489eaef21e docs(turbopack): Remove old architecture.md file from turbopack crate (#91473)
All the stuff in this file is either better documented elsewhere or out-of-date and misleading.
2026-03-17 16:24:52 -07:00
Benjamin WoodruffandGitHub 2d44d0dcd1 docs(turbopack): Migrate chunking docs from mdbook, with proof-reading and minor updates from claude (#91472)
Copied from https://turbopack-rust-docs.vercel.sh/turbopack/chunking.html

I had claude review it for correctness against the code. It found a few things where the names weren't correct or a trait was actually a struct, and it grouped things into sections. It also added some more details to a few sections, like the dev vs production chunking. I think this is a reasonable improvement.

I did a second pass with claude where I had it remove things it 1:1 duplicated with the existing rustdocs, and I had it add intradoc links.

Rendered:

![Screenshot 2026-03-16 at 14-40-34 turbopack_core _chunking - Rust.png](https://app.graphite.com/user-attachments/assets/1112b010-1d38-4604-b7f9-ec29816565b2.png)
2026-03-17 10:59:31 -07:00
Benjamin WoodruffandGitHub cdec4740ec docs(turbopack-core): Copy "layers" documentation from mdbook to rustdoc (#91350)
This is implemented as a dummy module that includes a markdown file. Clap does something similar in their rustdocs.

I had claude review the content for accuracy. It made a few modifications. I added rustdoc links by hand.

I converted the mermaid diagram to excalidraw and uploaded the image to vercel blob.

Copied from: https://turbopack-rust-docs.vercel.sh/turbopack/layers.html

Rendered:

![Screenshot 2026-03-13 at 17-13-58 turbopack_core _layers - Rust.png](https://app.graphite.com/user-attachments/assets/d237a05e-da03-405a-8f36-e17e0a351b14.png)
2026-03-17 09:46:39 -07:00
Benjamin WoodruffandGitHub 924d39dcef docs(turbopack): Fix all warnings in cargo doc --no-deps --workspace, and run rustdoc as part of our CI checks (#91339)
This gets `cargo doc` clean, and enables it in CI so that we won't regress it (I'm open to removing this if it proves to be annoying).

I also cleaned up some documentation and even found some dead code (deleted `turbopack/crates/turbopack-dev-server/src/source/conditional.rs`).
2026-03-16 16:42:24 -07:00
Benjamin WoodruffandGitHub 68dade4679 Turbopack: Remove old macos-compress script (#90967)
@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.
2026-03-14 01:25:49 +01:00
Benjamin WoodruffandGitHub d7de6f105c Turbopack: Remove the IntoTraitRef trait, make it an inherent method on Vc (#91223)
This is basically one-shot from Opus 4.6, with a minor tweak to the doc comment by hand.

Discussed this with @lukesandberg, and we realized that there's only one implementation of this trait, so it doesn't make a ton of sense as a trait.
2026-03-13 10:43:28 -07:00
Benjamin WoodruffandGitHub bdf11299c1 docs(turbopack): Move singleton pattern documentation from mdbook to rustdoc (#91175)
Copied over from https://turbopack-rust-docs.vercel.sh/turbo-engine/singleton.html with minimal changes.

![Screenshot 2026-03-10 at 5.53.09 PM.png](https://app.graphite.com/user-attachments/assets/34b36854-1643-4daf-a4e6-7baf43ef2790.png)
2026-03-12 16:34:17 -07:00
Benjamin WoodruffandGitHub 0b5f67d7b9 docs(turbopack): Migrate docs about traits from mdbook to rustdocs (#91170)
Migrates the content from https://turbopack-rust-docs.vercel.sh/turbo-engine/traits.html over to rustdocs, and tries to update/fix some minor mistakes our outdated docs.

![Screenshot 2026-03-10 at 14-38-39 value_trait in turbo_tasks - Rust.png](https://app.graphite.com/user-attachments/assets/5cc9c7d1-6260-45a9-8898-a5fdb85a5dc0.png)

![Screenshot 2026-03-10 at 14-39-33 VcValueTrait in turbo_tasks - Rust.png](https://app.graphite.com/user-attachments/assets/24448df5-8745-4568-82f1-294272e4417f.png)

![Screenshot 2026-03-10 at 14-39-05 VcValueType in turbo_tasks - Rust.png](https://app.graphite.com/user-attachments/assets/01ea2e00-7d7f-4759-b44f-e75a2a7fba9d.png)
2026-03-12 15:47:38 -07:00
Benjamin WoodruffandGitHub fc6bd9ea8d docs(turbopack): Merge task input docs from mdbook into rustdocs (#91136)
Copying content from https://turbopack-rust-docs.vercel.sh/turbo-engine/task_inputs.html

Removed some AI slop and some outdated stuff (`Value` is gone, we implemented `TaskInput` on `Arc`). Added a note about serialization of reference counted types.

Rendered:

![Screenshot 2026-03-09 at 21-07-44 TaskInput in turbo_tasks - Rust.png](https://app.graphite.com/user-attachments/assets/9035cabc-8781-485a-bb0c-155de8c4a8b3.png)
2026-03-12 11:42:06 -07:00
Benjamin WoodruffandGitHub 7fc7f1aed6 docs(turbopack): Move mdbook "tasks" docs over to turbo_tasks::function rustdoc (#91132)
Moving content from: https://turbopack-rust-docs.vercel.sh/turbo-engine/tasks.html

Very few changes, mostly updates to the introduction and links.

Rendered:

![Screenshot 2026-03-09 at 19-07-57 function in turbo_tasks - Rust.png](https://app.graphite.com/user-attachments/assets/ad7eb1d5-aee4-40ea-840d-7f17b59278dc.png)
2026-03-12 08:53:50 -07:00