APM

>Agent Skill

@cloudflare/update-v8

skillgit-workflow

Step-by-step guide for updating the V8 JavaScript engine in workerd, including patch rebasing, dependency updates, integrity hashes, and verification. Load this skill when performing or assisting with a V8 version bump.

javascriptgitdocumentation
apm::install
$apm install @cloudflare/update-v8
apm::skill.md
---
name: update-v8
description: Step-by-step guide for updating the V8 JavaScript engine in workerd, including patch rebasing, dependency updates, integrity hashes, and verification. Load this skill when performing or assisting with a V8 version bump.
---

## Updating V8 in workerd

V8 updates are high-risk changes that require careful patch management and human judgment for merge conflicts. This skill covers the full process. **Always confirm the target version with the developer before starting.**

See also: `docs/v8-updates.md` for the original reference document.

**Always** communicate and confirm with the developer at each step.
**Never** take irreversible actions (like dropping patches or updating hashes) without explicit confirmation.

---

### Prerequisites

- `depot_tools` installed and on `$PATH` ([setup guide](https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up))
- A local V8 checkout (outside the workerd repo to avoid confusing Bazel):
  ```sh
  mkdir v8 && cd v8 && fetch v8
  ```

---

### Step 1: Identify the target version

Check [chromiumdash.appspot.com](https://chromiumdash.appspot.com/) for the latest V8 version used by Chrome Beta. Confirm the target `<new_version>` with the developer.

Find the current version in `build/deps/v8.MODULE.bazel`:

```
VERSION = "14.5.201.6"    # example — check the actual file
```

We'll call this `<old_version>`.

### Step 2: Sync local V8 to the current workerd version

```sh
cd <path_to_v8>/v8
git checkout <old_version>
gclient sync
```

### Step 3: Apply workerd's patches onto a branch

```sh
git checkout -b workerd-patches
git am <path_to_workerd>/patches/v8/*
```

There are multiple patches in `patches/v8/`. These include workerd-specific customizations:

| Patch category  | Examples                                                                                              |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| Serialization   | Custom ValueSerializer/Deserializer format versions, proxy/function host object support               |
| Build system    | Windows/Bazel fixes, shared linkage, dependency path overrides (fp16, fast_float, simdutf, dragonbox) |
| Embedder hooks  | Promise context tagging, cross-request promise resolution, extra isolate embedder slot                |
| Bug workarounds | Memory leak assert disable, slow handle check disable, builtin-can-allocate workaround                |
| API additions   | `String::IsFlat`, `AdjustAmountOfExternalAllocatedMemory` exposure, additional Exception constructors |
| ICU/config      | ICU data export, googlesource ICU binding, verify_write_barriers flag                                 |

### Step 4: Rebase patches onto the new V8 version

```sh
git rebase --onto <new_version> <old_version>
```

**This is where most of the work happens.** Expect conflicts. Key guidance:

- **Build-system patches** (dependency paths, Bazel config) conflict most often as upstream V8 restructures its build.
- **API patches** (new methods on V8 classes) may conflict if upstream changed the surrounding code.
- **Always preserve workerd's intent** — understand what each patch does before resolving conflicts. The patch filenames are descriptive.
- **Do not drop patches** without explicit confirmation from the developer.
- **Do not auto-resolve conflicts** — flag them for human review. Merge conflicts in V8 patches almost always require human judgment.

### Step 5: Regenerate patches

```sh
git format-patch --full-index -k --no-signature --no-stat --zero-commit <new_version>
```

This produces numbered `.patch` files in the current directory.

### Step 6: Replace patches in workerd

**Always** confirm with the human before replacing patches. If any patches were dropped or added,
the human needs to review the changes.

```sh
rm <path_to_workerd>/patches/v8/*
cp *.patch <path_to_workerd>/patches/v8/
```

### Step 7: Update `build/deps/v8.MODULE.bazel`

Three things need updating:

1. **`VERSION`**: Set to `<new_version>`.

2. **`INTEGRITY`**: Compute the sha256 hash of the new tarball:

   ```sh
   curl -sL "https://github.com/v8/v8/archive/refs/tags/<new_version>.tar.gz" -o v8.tar.gz
   openssl dgst -sha256 -binary v8.tar.gz | openssl base64 -A
   ```

   Format: `"sha256-<base64_hash>="`. Alternatively, attempt a build and copy the expected hash from Bazel's mismatch error.

3. **`PATCHES`**: Update the list if patches were added, removed, or renamed. The list must match the filenames in `patches/v8/` exactly, in order.

### Step 8: Update V8's dependencies

V8 depends on several libraries that are pinned in `build/deps/v8.MODULE.bazel` and `build/deps/deps.jsonc`. Check the local V8 checkout's `DEPS` file for commit versions:

```sh
cat <path_to_v8>/v8/DEPS
```

Dependencies to check and update:

| Dependency                      | Where                                      | Notes                                                          |
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------------- |
| `com_googlesource_chromium_icu` | `v8.MODULE.bazel` (git_repository commit)  | Chromium fork; update commit from V8's DEPS                    |
| `perfetto`                      | `deps.jsonc` (managed by `update-deps.py`) | V8 depends via Chromium; safe to bump to latest GitHub release |
| `simdutf`                       | `deps.jsonc` (managed by `update-deps.py`) | V8 depends via Chromium; safe to bump to latest GitHub release |

For dependencies in `deps.jsonc`, you can use the update script:

```sh
python3 build/deps/update-deps.py perfetto
python3 build/deps/update-deps.py simdutf
```

This fetches the latest version, computes integrity hashes, and regenerates the `gen/` MODULE.bazel fragments. **Do not hand-edit files in `build/deps/gen/`.**

### Step 9: Build and test

```sh
# Full build
just build

# Full test suite
just test
```

Watch for:

- **Build failures from V8 API changes**: V8 may deprecate or change APIs between versions. Search for deprecation warnings in the build output. Common areas affected:
  - `src/workerd/jsg/` — V8 binding layer, most directly affected
  - `src/workerd/api/` — APIs that interact with V8 types directly
  - `src/workerd/io/worker.c++` — Isolate creation and configuration

- **Test failures from behavior changes**: V8 may change observable JS behavior. Check:
  - `just test //src/workerd/jsg/...` — JSG binding tests
  - `just test //src/workerd/api/tests/...` — API tests
  - Node.js compatibility tests (`just node-test`)
  - Web Platform Tests (`just wpt-test`)

- **New V8 deprecation warnings**: These are future breakage signals. Document them in the PR description even if tests pass.

### Step 10: Commit and submit

Prompt the user to commit the changes and push for review.

**Never** push the branch for review without a human review of the patch changes.

You **May** prepare the draft PR text for the user. The PR should include:

- Updated `build/deps/v8.MODULE.bazel` (version, integrity, patches list)
- Updated patches in `patches/v8/`
- Updated dependency versions if changed
- Any C++ fixes for V8 API changes
- PR description listing: old version, new version, patches that required conflict resolution, any deprecation warnings observed, and any behavior changes noted

---

### Checklist

- [ ] Target V8 version confirmed with developer
- [ ] Local V8 checked out and synced to old version
- [ ] workerd patches applied and rebased onto new version
- [ ] Conflicts resolved with human review (no auto-resolution)
- [ ] Patches regenerated with `git format-patch`
- [ ] Old patches replaced with new patches in `patches/v8/`
- [ ] `VERSION` updated in `v8.MODULE.bazel`
- [ ] `INTEGRITY` updated in `v8.MODULE.bazel`
- [ ] `PATCHES` list updated if patches added/removed/renamed
- [ ] V8 dependencies checked and updated (ICU, perfetto, simdutf)
- [ ] `just build` succeeds
- [ ] `just test` passes (or failures documented and explained)
- [ ] No new patches dropped without explicit confirmation
- [ ] PR description documents version change, conflict resolutions, and deprecation warnings

---

### Troubleshooting

**Bazel integrity mismatch**: If you see `expected sha256-... but got sha256-...`, copy the "got" hash into the `INTEGRITY` field. This happens when the hash was computed incorrectly or the tarball was re-generated by GitHub.

**Patch won't apply**: A patch that applied cleanly during `git am` but fails in Bazel means the `git format-patch` output differs from what Bazel expects. Verify you used `--full-index -k --no-signature --no-stat --zero-commit` flags. Also verify patch order matches the `PATCHES` list.

**ICU build failures**: ICU is a Chromium fork fetched via `git_repository`. If the commit in `v8.MODULE.bazel` is wrong, you'll see missing-file or compilation errors in ICU. Cross-reference with V8's `DEPS` file for the correct commit.

**`update-deps.py` fails**: The script requires network access to fetch versions. If a dependency's GitHub release format changed, you may need to update the version manually in `deps.jsonc` and run `python3 build/deps/update-deps.py` to regenerate hashes.