Visitar URL original
fix(build): make Firefox builds reproducible across paths and remove the runtime-chunk mangling workaround by gauthierpetetin · Pull Request #46845 · MetaMask/metamask-extension · GitHub
Skip to content

fix(build): make Firefox builds reproducible across paths and remove the runtime-chunk mangling workaround - #46845

Draft
gauthierpetetin wants to merge 14 commits into
mainfrom
fix/amo-build-determinism
Draft

gauthierpetetin wants to merge 14 commits into
mainfrom
fix/amo-build-determinism

Conversation

@gauthierpetetin

@gauthierpetetin gauthierpetetin commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

Firefox (AMO) reviewers rebuild MetaMask from source and compare it byte for byte with what we
submitted. runtime.js sometimes came out different (190 bytes, all c/l swaps in variable
names), which has been blocking Firefox releases. #46236 worked around it by not minifying
variable names in that one file.

This PR fixes the four root causes and removes the workaround. All four are ways that something
build-specific (the absolute path of the build directory, or which thread happened to compile a
file) leaked into webpack's module fingerprints, which in turn tipped SWC's letter-frequency-based
variable naming. Each one is explained in plain language, for readers with no context, in a
comment:

The commits are split one per fix, so each can be reviewed on its own.

Proof

We built this branch 20 times on CI, each time in a directory with a different name, and compared
the outputs: all 20 builds are byte-identical. The same test on v13.47.1 as released gives two
different runtime.js files (11 builds one way, 9 the other).
CI run

Changelog

CHANGELOG entry: null

Related issues

Fixes: INFRA-3920

Manual testing steps

  1. Make two copies of this branch in directories with clearly different name lengths.
  2. In each: yarn install, then
    SOURCE_DATE_EPOCH=1700000000 ENABLE_MV3=false yarn webpack:lavamoat:build:mv2 --env production
  3. diff -r <copyA>/dist/firefox <copyB>/dist/firefox and verify there are no differences.
  4. Load dist/firefox as a temporary add-on in Firefox (about:debugging), unlock or onboard, and
    verify the UI renders with no LavaDome / lockdown / module is undefined console errors.
  5. Open the home, popup and notification pages and confirm scripts, styles and images load.

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

🤖 Generated with Claude Code

gauthierpetetin and others added 12 commits October 1, 2026 15:49
Brings in swc-project/swc#12129 (drop spans of cached `globals` values) and
#12166 (key the optimizer env cache by its configured values). Together they
stop SWC's `process.env` inlining from attaching stale, thread-timing
dependent source-map positions to inlined values, which perturbed module
hashes between otherwise identical builds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Extract TYPESCRIPT_NON_TSX_FILE_RE and TYPESCRIPT_TSX_FILE_RE so webpack
and envValidationLoader reuse the same .ts/.tsx matching rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
Match the SWC loader split so webpack unit tests expect separate
non-TSX and TSX React Refresh rules.

Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid hanging when Save closes a modal before the detached wait starts,
which the SWC 1.16 build made consistently reproducible.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the 1s custom-network block-tracker interval in test builds so subscribe E2E does not wait on the 20s production poll.

Co-authored-by: Cursor <cursoragent@cursor.com>
Satisfy webpack tsc for the IN_TEST npm loader map, and drop trailing newlines so regenerated MV2/MV3 policies match CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reverts the runtime-chunk special case from #46236. With the two root causes
of the `runtime.[contenthash].js` non-determinism fixed at the source (the swc
loader no longer leaks the absolute build path into module hashes, and
`@swc/core` 1.16.2 no longer attaches stale positions to inlined `process.env`
values), SWC's frequency-ordered mangling is deterministic again and the
runtime chunk can be mangled like every other chunk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
webpack only rewrites a loader source map's `sources` relative to the build
context when the map is an object (`NormalModule.contextifySourceMap` passes
strings through), and the map is then hashed into `buildInfo.hash` verbatim.
Because `sourceFileName` is the absolute `resourcePath`, returning SWC's map
as a JSON string made every module's hash, and so every chunk's provisional
`[contenthash]`, depend on where the project lives on disk. The runtime chunk
embeds those provisional hashes as string literals, which shifted SWC's
character-frequency mangling alphabet between build paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`html-bundler-webpack-plugin`'s loader turns `<script src="./x.ts">` into
`require('/absolute/path/to/x.ts')` in the generated HTML entry module.
webpack resolves it fine, but it hashes that source text into
`buildInfo.hash`, so the seven HTML entry chunks' provisional
`[contenthash]` depended on where the project lives on disk — enough to
tip the runtime chunk's character-frequency mangling alphabet on v13.47.1
even with the swc loader and `@swc/core` fixes in place.

Extend our existing yarn patch so `requireExpression()` emits the request
relative to the template's directory (`require('../../scripts/x.ts')`),
which webpack resolves identically.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`yarn lavamoat:auto`. With swc-project/swc#12166 the optimizer's env cache is
keyed by its configured values, so `node_modules` code no longer inherits the
first-party `process.env` inlining and the policy generator now sees
`process.env.NODE_ENV` in those packages (webpack's `optimization.nodeEnv`
still replaces it in the emitted bundle).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`reactCompilerLoaderWrapper` recorded the compiler's per-function events on
`module.buildMeta`, including the absolute `filename`. webpack hashes
`JSON.stringify(buildMeta)` into every module's `buildInfo.hash`, so the ~1,800
`ui/**` modules that go through the React Compiler had hashes that depended on
the absolute build path — and on whether the loader happened to run in-process
or in a thread-loader worker (where `this._module` is unavailable and nothing
is recorded), which thread-loader decides at runtime when its pool is
unavailable. Both shifted the provisional chunk hashes embedded in the
runtime chunk and so SWC's character-frequency mangling alphabet.

Store the events on `buildInfo` instead, which webpack does not hash, and
record the filename relative to the build context.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@socket-security

socket-security Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​swc/​core@​1.16.1 ⏵ 1.16.29210010095 -1100

View full report

@socket-security

socket-security Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Warning

MetaMask internal reviewing guidelines:

  • Do not ignore-all
  • Each alert has instructions on how to review if you don't know what it means. If lost, ask your Security Liaison or the supply-chain group
  • Copy-paste ignore lines for specific packages or a group of one kind with a note on what research you did to deem it safe.
    @SocketSecurity ignore npm/PACKAGE@VERSION
Priority Alert  (click "▶" to expand/collapse) Action
Low priority
Potential code anomaly (AI signal): npm @swc/core is 66.0% likely to have a medium risk anomaly

Notes: This module is a configuration loader/merger, but it performs dynamic module loading via require() using a runtime-derived specifier/path from compileBundleOptions(config). The default behavior (config undefined -> require(".")) can cause loading of whatever Node resolves from the current working directory, which is risky if the caller’s config input or the process working directory can be influenced. Aside from require()-driven execution and error-message information disclosure, the snippet shows no overt malicious payload behavior.

Confidence: 0.66

Severity: 0.60

From: package.json → npm/@swc/core@1.16.2

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@swc/core@1.16.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn

View full report

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gauthierpetetin

gauthierpetetin commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Background: why four unrelated bugs all showed up as the same c/l swap

Read this once. The four fix comments below each refer back to it.

The symptom

Mozilla reviewers rebuild MetaMask from source and compare their result to our .zip, byte for byte. One file out of about 250, runtime.js, sometimes differed. It differed by exactly 190 bytes, every one of them the letter c and the letter l trading places in variable names:

  ours:      let c=[];  ...  for(var c=1/0 ... l=!0 ... if(l){
  reviewer:  let l=[];  ...  for(var l=1/0 ... c=!0 ... if(c){

Same code, same length, same behaviour. Different checksum, failed review.

Step 1: short variable names are assigned by a popularity contest

To shrink files, our build (via a tool called SWC) renames every variable to one letter. It does not go alphabetically. It counts how often each letter already appears in the file and gives the most-used variable the most popular letter, because that compresses better with gzip.

For runtime.js the ranking came out as:

  rank:    1   2   3   4   5   6   7     8   9     10  11  12
  letter:  e   t   r   a   s   o   n  │  c   l  │  i   m   p
  count:  …   …   …   …   …   …   …  │7194 7269│  …   …   …
                                       └────────┘
                                   essentially TIED

So one extra c anywhere in the file flips which letter wins rank 8, and every variable that would have been c becomes l and vice versa. That is the 190 swaps.

Step 2: runtime.js contains about 3,200 hex characters that are temporary

runtime.js is the file that knows where all the other files live, so it holds a table of their names, which contain hashes written in hex:

{ "46":"e23a6c9490339b890118", "110":"682055cc237ca3ec5279", ... }   // ~160 entries
          hex alphabet: 0 1 2 3 4 5 6 7 8 9 a b c d e f
                                            ↑
                                   has 'c', never 'l'

Two things about these hashes matter:

  1. They are provisional. webpack computes them early, SWC renames variables while they are still in the file, and only afterwards webpack replaces them with the final hashes. So they influence the popularity contest and then disappear. That is why the two differing files showed no visible cause.
  2. Each one is a fingerprint of a chunk of our code, computed from everything webpack knows about the modules inside: their code, their source maps, and some metadata.

Step 3: anything that leaks into a module fingerprint leaks into the alphabet

  something build-specific gets into a module's fingerprint
        ↓
  that chunk's provisional hash changes (20 new hex chars)
        ↓
  the number of 'c' in runtime.js's table shifts by a few
        ↓
  'c' vs 'l' popularity flips  →  190 variable names swap
        ↓
  webpack overwrites the provisional hashes → evidence gone, swap remains

The four fixes in this PR each close one way that something build-specific (the absolute path of the build directory, or which thread happened to compile a file) was getting into those fingerprints. None of them touches SWC. SWC was doing exactly what it is designed to do.

With all four closed, the fingerprints depend only on the source code, so the contest has the same outcome on every machine and in every directory, and the special case from #46236 (not renaming variables in runtime.js at all) can be removed.

@gauthierpetetin

gauthierpetetin commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix 1 of 4: the build folder's name was baked into every module's fingerprint

File: development/webpack/utils/loaders/swcLoader.ts (3 lines changed)
Affected: 11,331 of 11,435 modules, essentially everything.

What was happening

When our swc loader compiles a file, it hands webpack two things: the compiled code and a source map (the debugging metadata that says "this output line came from that input line"). The source map names the original file it describes, and SWC writes that as the full absolute path:

{ "sources": ["/Users/alice/Repositories/metamask-extension/app/scripts/background.ts"], ... }

webpack knows this is a problem and normally scrubs it: it rewrites the path relative to the project (webpack://./scripts/background.ts) before using the map in the module's fingerprint. But it only does that when the loader gives it the map as a parsed object. Our loader gave it the map as a JSON string, and webpack's scrubbing code starts with:

if (typeof sourceMap === "string" || !Array.isArray(sourceMap.sources)) {
  return sourceMap;        // ← hands the string back untouched
}

So the raw string, absolute path included, went straight into the fingerprint of every module we compile:

  /home/runner/work/metamask-extension/…      (our CI)
  /output/comparison_v13.47.1/metamask-…      (the reviewer's script)
            different text → every fingerprint differs → see background

This is why the reviewers' rebuild, which runs in a different directory than our release build, kept failing while our own CI builds never did.

The fix

Give webpack the parsed object instead of the string, so its own scrubbing runs:

-transform(src, options).then(({ code, map }) => cb(null, code, map), cb);
+transform(src, options).then(
+  ({ code, map }) => cb(null, code, map === undefined ? map : JSON.parse(map)),
+  cb,
+);

The existing unit test asserted the string form. It now asserts the object form and explains why.

How we know it works

Per-module fingerprint dumps of the same tree built in a 43-character and a 115-character path: before the fix, 11,331 modules had different fingerprints. After it, only the modules affected by fixes 2 to 4 still did.

@gauthierpetetin

gauthierpetetin commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix 2 of 4: @swc/core remembered where the first file's values came from

Change: @swc/core 1.13.3 → 1.16.2, plus three adjustments that version needs (listed below).
Affected: every module that mentions process.env.SOMETHING, a few hundred, varying from build to build.

What was happening

Our build replaces process.env.NODE_ENV, process.env.IN_TEST, etc. with their literal values while compiling, so that if (process.env.IN_TEST) can be optimised away. SWC does this substitution for us.

To avoid re-parsing the same handful of values thousands of times, SWC parsed them once per process and cached the result. The problem: a parsed value carries a position ("I came from byte N of the file being compiled"), and the cached copy kept the position from whichever file happened to be compiled first.

  worker starts   →  compiles   a.tsx  (12 KB)   → parses 'production' @ byte 12,340, caches it
                  →  compiles   b.tsx  (3 KB)    → reuses cache: 'production' "@ byte 12,340"
                                                    …but b.tsx only has 3,000 bytes
                                                    → one source-map entry points at garbage

That one garbage entry changes b.tsx's source map, which changes its fingerprint. And which file is compiled first depends on thread scheduling, so the set of affected modules differed from one build to the next, even in the same directory. This is the part of the problem that fix 1 alone cannot explain: builds in the same folder still disagreed with each other occasionally.

The fix

Already fixed upstream. swc-project/swc#12129 drops the stale positions from cached values, and #12166 keys the cache correctly (it was keyed on the wrong option, so even packages we had configured with no substitutions were silently getting ours). Both shipped in 1.16.2. This PR bumps to it and brings the three adjustments that version needs:

  • .ts and .tsx files get separate loaders (1.16 parses TypeScript generics as JSX when tsx: true).
  • IN_TEST is now substituted explicitly for node_modules code. Previously it leaked in by accident through the mis-keyed cache, and @metamask/network-controller relied on it.
  • LavaMoat policies regenerated. They gain "process.env.NODE_ENV": true for about 35 third-party packages for the same reason: those packages no longer inherit our substitution, so the policy generator now sees the reference. At runtime webpack still substitutes it, so the bundle behaves exactly as before. The only process.env.NODE_ENV left in the output is inside the policy text itself.

How we know it works

Same tree built twice at the same path: before, 15 to 230 modules had different fingerprints (always only the source-map mappings field). After the bump, 0 of 14,431.

@gauthierpetetin

gauthierpetetin commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix 3 of 4: the HTML pages were compiled with absolute paths inside them

File: our existing yarn patch for html-bundler-webpack-plugin (.yarn/patches/html-bundler-webpack-plugin-npm-4.23.2-*.patch)
Affected: 7 modules, the HTML entry pages (home.html, popup.html, background.html, notification.html, …).

What was happening

Our HTML pages are webpack entry points. html-bundler-webpack-plugin turns each one into a small JavaScript module so webpack can track the scripts and images it references. For <script src="../../scripts/load/bootstrap.ts"> it generated:

module.exports = '…<script src="' + require('/Users/alice/Repositories/metamask-extension/app/scripts/load/bootstrap.ts') + '" defer>…'
                                              ↑ absolute path, written into the module's code

webpack resolves that require() to a module id, so the final HTML is path-independent. But the module's code, with the absolute path, is what goes into its fingerprint. Seven HTML modules give seven provisional hashes, about 140 hex characters in runtime.js's table that changed with the build directory.

Only 7 modules out of 11,435, but the margin between c and l was one character, so this alone was enough to flip it.

The fix

Make the generated code say require('../../scripts/load/bootstrap.ts'), relative to the HTML file. webpack resolves that to exactly the same module, because a module's requests are always resolved relative to its own directory.

The plugin, however, also executes that generated module itself later, with its own mini require(), to splice in the final output filenames. That mini require() looks scripts up by their absolute path. So the patch has two halves, and a scope rule:

  compile time (loader)                        render time (plugin)
  ───────────────────────                      ──────────────────────────────────
  emit  require('../../scripts/x.ts')   ───▶   turn it back into /abs/…/scripts/x.ts
  only for requests made by an HTML            before looking the script up
  entry page itself

The scope rule matters: a stylesheet's url(../webfonts/font.woff2) is compiled in one file (a Sass partial) but executed under another (the root stylesheet), so a relative path would resolve against the wrong directory. Those stay absolute. They are not webpack modules whose fingerprint matters, so nothing is lost.

How we know it works

With this fix, the two-path fingerprint comparison shows 0 differing modules. All 663 output files, including the HTML pages, are byte-identical across paths, and the pages' <script src> attributes resolve to real output files.

@gauthierpetetin

gauthierpetetin commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Fix 4 of 4: React Compiler statistics were being hashed along with the code

Files: development/webpack/utils/loaders/reactCompilerLoaderWrapper.ts, …/plugins/ReactCompilerPlugin/index.ts (plus comments in …/loaders/reactCompilerLoader.ts)
Affected: about 1,800 modules, every file under ui/ that goes through the React Compiler.

What was happening

Our React Compiler wrapper collects statistics for the --reactCompilerVerbose report. For each function it compiles, it records an event (compiled, skipped or error, the line and column, and the file's absolute path) and stores the list on the module as buildMeta.__reactCompilerStatus__.

buildMeta is the wrong drawer. webpack treats it as "facts about what this module is" and includes JSON.stringify(buildMeta) in the module's fingerprint. So:

"__reactCompilerStatus__": { "events": [
  { "filename": "/home/runner/work/metamask-extension/metamask-extension/work/i1-xxxxxx/metamask-extension/ui/contexts/assetPolling.tsx", … }
]}

made 1,800 fingerprints depend on the build directory. Worse, they also depended on timing. The wrapper can only record events when it runs in the main process (it needs this._module, which thread-loader workers do not have). thread-loader normally runs these loaders in worker processes, but when its worker pool is unavailable it silently falls back to running them in-process. On a fast machine the pool stays alive and nothing is ever recorded. On a slow 2-core CI runner the pool goes idle and gets disposed mid-build, and a timing-dependent subset of modules gets the statistics recorded:

  build 1:  assetPolling.tsx → buildMeta has __reactCompilerStatus__  (path i1-xxxxxx)
  build 2:  assetPolling.tsx → buildMeta has __reactCompilerStatus__  (path i2-xxxxxxxxxxxx)
  build 5:  assetPolling.tsx → no __reactCompilerStatus__ at all

This is why this one can only be seen on CI: on a laptop it is literally not there.

The fix

Store the events on buildInfo instead, webpack's drawer for "things that happened while building this module", which is not hashed, and record the filename relative to the project root. ReactCompilerPlugin reads from buildInfo now. The verbose report is unchanged.

-  const buildMeta = this._module?.buildMeta
+  const buildInfo = this._module?.buildInfo
   …
-          filename,
+          filename: rootContext ? relative(rootContext, filename) : filename,
   …
-        buildMeta[REACT_COMPILER_STATUS_KEY] = { events };
+        buildInfo[REACT_COMPILER_STATUS_KEY] = { events };

How we know it works

This branch, built unmodified at 20 different paths on the CI runners: 1 runtime.js and 1 whole-dist digest across all 20. Without this fix, the same 20 builds split 16 / 4.

@metamask-ci

metamask-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

✨ Files requiring CODEOWNER review ✨

👨‍🔧 @MetaMask/extension-platform (9 files, +125 -60)
  • 📁 development/
    • 📁 webpack/
      • 📁 test/
        • 📄 loaders.swcLoader.test.ts +8 -2
        • 📄 webpack.config.test.ts +6 -2
      • 📁 utils/
        • 📁 loaders/
          • 📄 envValidationLoader.ts +3 -2
          • 📄 reactCompilerLoader.ts +7 -6
          • 📄 reactCompilerLoaderWrapper.ts +15 -8
          • 📄 swcLoader.ts +12 -1
        • 📁 plugins/
          • 📁 ReactCompilerPlugin/
            • 📄 index.ts +8 -8
          • 📄 helpers.ts +16 -25
        • 📄 webpack.config.ts +50 -6

📜 @MetaMask/policy-reviewers (8 files, +640 -120)
  • 📁 lavamoat/
    • 📁 webpack/
      • 📁 mv2/
        • 📁 beta/
          • 📄 policy.json +79 -14
        • 📁 experimental/
          • 📄 policy.json +79 -14
        • 📁 flask/
          • 📄 policy.json +79 -14
        • 📁 main/
          • 📄 policy.json +79 -14
      • 📁 mv3/
        • 📁 beta/
          • 📄 policy.json +81 -16
        • 📁 experimental/
          • 📄 policy.json +81 -16
        • 📁 flask/
          • 📄 policy.json +81 -16
        • 📁 main/
          • 📄 policy.json +81 -16

Tip

Follow the policy review process outlined in the LavaMoat Policy Review Process doc before expecting an approval from Policy Reviewers.


👨‍🔧 @itsyoboieltr (9 files, +125 -60)
  • 📁 development/
    • 📁 webpack/
      • 📁 test/
        • 📄 loaders.swcLoader.test.ts +8 -2
        • 📄 webpack.config.test.ts +6 -2
      • 📁 utils/
        • 📁 loaders/
          • 📄 envValidationLoader.ts +3 -2
          • 📄 reactCompilerLoader.ts +7 -6
          • 📄 reactCompilerLoaderWrapper.ts +15 -8
          • 📄 swcLoader.ts +12 -1
        • 📁 plugins/
          • 📁 ReactCompilerPlugin/
            • 📄 index.ts +8 -8
          • 📄 helpers.ts +16 -25
        • 📄 webpack.config.ts +50 -6

@metamask-ci

metamask-ci Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor
Builds ready [c802969]
⚡ Performance Benchmarks (Total: 🟢 8 pass · 🟡 3 warn · 🔴 2 fail)

Baseline (latest main): 171ed20 | Date: 7/28/2026 | Pipeline: 37663979844 | Baseline logs

Metricschrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🔴 load_new_account(p95) [CI log]🔴 load_new_account(p95) [CI log]

Regressions (🔴 2 failures)

Interaction Benchmarks · Samples: 5 🔴 2
Benchmarkchrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🔴 [CI log]
🔴 load_new_account
🔴 [CI log]
🔴 load_new_account
confirmTx
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
bridgeUserActions
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↑ loadNewAccount/load_new_account: +417%
  • ↑ loadNewAccount/total: +417%
  • ↑ confirmTx/longTaskCount: +33%
  • ↑ confirmTx/longTaskTotalDuration: +67%
  • ↑ confirmTx/longTaskMaxDuration: +73%
  • ↑ confirmTx/tbt: +93%
  • ↑ confirmTx/fcp: +11%
  • ↑ confirmTx/lcp: +10%
  • ↑ bridgeUserActions/bridge_load_page: +53%
  • ↑ bridgeUserActions/bridge_load_asset_picker: +80%
  • ↑ bridgeUserActions/longTaskCount: +67%
  • ↑ bridgeUserActions/longTaskTotalDuration: +64%
  • ↑ bridgeUserActions/tbt: +45%
  • ↑ bridgeUserActions/total: +10%
  • ↑ loadNewAccount/load_new_account: +420%
  • ↑ loadNewAccount/total: +420%
  • ↓ loadNewAccount/inp: -32%
  • ↓ loadNewAccount/fcp: -62%
  • ↑ loadNewAccount/lcp: +1308%
  • ↓ confirmTx/longTaskCount: -100%
  • ↓ confirmTx/longTaskTotalDuration: -100%
  • ↓ confirmTx/longTaskMaxDuration: -100%
  • ↓ confirmTx/tbt: -100%
  • ↓ confirmTx/inp: -32%
  • ↓ confirmTx/fcp: -66%
  • ↑ confirmTx/lcp: +900%
  • ↑ bridgeUserActions/bridge_load_page: +168%
  • ↑ bridgeUserActions/bridge_load_asset_picker: +167%
  • ↓ bridgeUserActions/bridge_search_token: -12%
  • ↓ bridgeUserActions/longTaskCount: -100%
  • ↓ bridgeUserActions/longTaskTotalDuration: -100%
  • ↓ bridgeUserActions/longTaskMaxDuration: -100%
  • ↓ bridgeUserActions/tbt: -100%
  • ↑ bridgeUserActions/total: +25%
  • ↑ bridgeUserActions/inp: +15%
  • ↓ bridgeUserActions/fcp: -65%
  • ↑ bridgeUserActions/lcp: +1316%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 loadNewAccount/FCP: p75 1.9s
  • 🟡 confirmTx/FCP: p75 1.9s
  • 🟡 bridgeUserActions/FCP: p75 1.9s
Startup Benchmarks · Samples: 100
Benchmarkchrome-webpackfirefox-webpack
startupStandardHome
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↑ startupStandardHome/uiStartup: +10%
  • ↑ startupStandardHome/domInteractive: +12%
  • ↑ startupStandardHome/firstPaint: +12%
  • ↓ startupStandardHome/firstReactRender: -99%
  • ↓ startupStandardHome/initialActions: -33%
  • ↑ startupStandardHome/setupStore: +29%
  • ↑ startupStandardHome/numNetworkReqs: +10%
  • ↑ startupStandardHome/fcp: +10%
  • ↓ startupStandardHome/uiStartup: -42%
  • ↓ startupStandardHome/load: -38%
  • ↓ startupStandardHome/domContentLoaded: -38%
  • ↓ startupStandardHome/domInteractive: -57%
  • ↓ startupStandardHome/backgroundConnect: -37%
  • ↓ startupStandardHome/firstReactRender: -99%
  • ↓ startupStandardHome/initialActions: -50%
  • ↓ startupStandardHome/loadScripts: -38%
  • ↑ startupStandardHome/setupStore: +34%
  • ↓ startupStandardHome/fcp: -53%
  • ↓ startupStandardHome/lcp: -37%
User Journey Benchmarks · Samples: 5 · mock API
Benchmarkchrome-webpackfirefox-webpack
onboardingImportWallet
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]
onboardingNewWallet
[Sentry log · main/release]
🟢 [CI log]🟡 [CI log]
🟡 total

📈 Results compared to the previous 5 runs on main

  • ↑ onboardingImportWallet/srpButtonToSrpForm: +39%
  • ↑ onboardingImportWallet/confirmSrpToPwForm: +41%
  • ↑ onboardingImportWallet/pwFormToMetricsScreen: +23%
  • ↑ onboardingImportWallet/metricsToWalletReadyScreen: +23%
  • ↓ onboardingImportWallet/doneButtonToHomeScreen: -94%
  • ↓ onboardingImportWallet/openAccountMenuToAccountListLoaded: -75%
  • ↓ onboardingImportWallet/longTaskCount: -86%
  • ↓ onboardingImportWallet/longTaskTotalDuration: -96%
  • ↓ onboardingImportWallet/longTaskMaxDuration: -93%
  • ↓ onboardingImportWallet/tbt: -100%
  • ↓ onboardingImportWallet/total: -89%
  • ↑ onboardingNewWallet/srpButtonToPwForm: +41%
  • ↑ onboardingNewWallet/createPwToRecoveryScreen: +16%
  • ↑ onboardingNewWallet/skipBackupToMetricsScreen: +23%
  • ↑ onboardingNewWallet/agreeButtonToOnboardingSuccess: +22%
  • ↓ onboardingNewWallet/doneButtonToAssetList: -75%
  • ↓ onboardingNewWallet/longTaskCount: -38%
  • ↓ onboardingNewWallet/longTaskTotalDuration: -75%
  • ↓ onboardingNewWallet/longTaskMaxDuration: -48%
  • ↓ onboardingNewWallet/tbt: -95%
  • ↓ onboardingNewWallet/total: -71%
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-webpack
dappPageLoad
[Sentry log · main/release]
🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↓ dappPageLoad/pageLoadTime: -57%
  • ↓ dappPageLoad/firstPaint: -41%
  • ↓ dappPageLoad/firstContentfulPaint: -41%
Bundle Size Diffs [🚀 Bundle size reduced!]
Status Bundle Total Diff Change
✅ background 15.49 MiB -74.31 KiB -0.47%
✅ ui 18.9 MiB -112.78 KiB -0.58%
✅ common 0 Bytes 0 Bytes 0.00%
✅ other 1.68 MiB -3.93 KiB -0.23%
✅ content scripts 1.97 MiB -4.28 KiB -0.21%
✅ zip 22.79 MiB -3.92 KiB -0.02%

@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
52.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

This branch was successfully deployed

1 active deployment
pr-comment — c802969e Deployed Oct 7, 2026 by gauthierpetetin via Publish prerelease / Publish prerelease #168186
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size-M team-extension-platform Extension Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant