Skip to content

fix(wxt): tolerate the content script reload race in MV3 dev - #2610

Open
bompus wants to merge 1 commit into
wxt-dev:mainfrom
bompus:fix/content-script-reload-race
Open

fix(wxt): tolerate the content script reload race in MV3 dev#2610
bompus wants to merge 1 commit into
wxt-dev:mainfrom
bompus:fix/content-script-reload-race

Conversation

@bompus

@bompus bompus commented Sep 4, 2026

Copy link
Copy Markdown

Overview

In MV3 dev, reloadManifestContentScriptMv3 registers a content script with a check-then-act sequence: getRegisteredContentScripts(), then registerContentScripts() if the id is absent. When two wxt:reload-content-script events for the same entrypoint arrive close together, both read an empty registration and both register; the loser throws Duplicate script ID 'wxt:content-scripts/<name>.js'.

Nothing catches it. reloadContentScript calls void reloadContentScriptMv3(payload), and the WebSocket listener does not await it, so the rejection lands as an unhandled promise rejection in the service worker.

This PR catches the duplicate-id rejection inside reloadManifestContentScriptMv3 and returns. The winner is registering the identical script and reloads the matching tabs once its registration lands, so the loser has nothing left to do.

Two alternatives that look reasonable and are not — both tried against a real dev session:

  • Falling back to updateContentScripts throws Script with ID '…' does not exist or is not fully registered, because the winner's registration is still in flight. That rejection aborts before reloadTabsForContentScript, and the script is left unregistered.
  • Swallowing the error and continuing to reloadTabsForContentScript reloads the tab before the winner's registration lands, so the page comes back without the content script.

The comment in the diff records both so neither gets reintroduced.

Manual Testing

The race is timing-dependent, so it does not reproduce on demand. It shows up on an MV3 project in wxt dev where several content script entrypoints share source, so one save emits multiple reload events in quick succession.

I have been running this change as a local patch against wxt@0.21.4 in a project whose Playwright suite records service-worker unhandled rejections and fails any test that sees one. Before the change, Duplicate script ID fired roughly twice per 23-spec run. After it, three consecutive runs recorded no Duplicate script ID, no not fully registered, and no other rejection; all 23 specs passed each time.

There are no existing tests under packages/wxt/src/virtual/, so none were added here. Happy to add one if there is a preferred harness for the virtual entrypoints.

Related Issue

This PR closes #2609

@bompus
bompus requested a review from aklinker1 as a code owner September 4, 2026 09:07
@netlify

netlify Bot commented Sep 4, 2026

Copy link
Copy Markdown

Deploy Preview for creative-fairy-df92c4 ready!

Name Link
🔨 Latest commit d508883
🔍 Latest deploy log https://app.netlify.com/projects/creative-fairy-df92c4/deploys/6a9a8a5c7eca1e0008fb0363
😎 Deploy Preview https://deploy-preview-2610--creative-fairy-df92c4.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the pkg/wxt Includes changes to the `packages/wxt` directory label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg/wxt Includes changes to the `packages/wxt` directory

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate script ID: content script reload races itself in MV3 dev, rejects unhandled

1 participant