A Manifest V3 extension's background script is a service worker. Chrome stops it after about 30 seconds without events and starts it again for the next one. Code written for an MV2 background page assumes it runs forever, so it works while you click around and breaks later: counters reset, timers never fire, and document, localStorage and XMLHttpRequest don't exist. Agents still write chrome.browserAction, tabs.executeScript, getBackgroundPage and blocking webRequest too.
This loadout pairs a Cursor rule and AGENTS.md (what a service worker isn't, and the API replacements) with a skill that checks the work. A standard-library Python scanner reports MV2 APIs and things a worker can't do as errors, and lifecycle risks (globals, long timers, nested listeners, keep-alive hacks) as warnings. A Playwright test then stops the worker through the DevTools protocol and uses the extension again. The verifier runs it in Playwright's Chromium: storage-backed state survives the restart, and the same extension with a global counter resets to 0. It works alongside the Chrome extension (Manifest V3) loadout, which covers manifests, permissions and store review.
What's in it
- Scanner flags MV2 APIs and DOM, localStorage or XHR in the worker
- Warns on globals as state, long timers and listeners below the top level
- Playwright test stops the service worker through CDP, then retries
- Shows a global counter resetting to 0 after a real worker restart
- API table from browserAction, executeScript and blocking webRequest to MV3
You'll need
- Tools:
node,python3
Verification
| Check | Result | What we found |
|---|---|---|
| Listinglisting.yaml matches the schema and declares every file in the folder. | Pass | Schema valid; 11 files declared, none missing or extra
|
| Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. | Pass | All 11 files parse; front matter valid |
| AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. | Pass | CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
|
| Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. | Pass | Correct paths for all 6 agents; layouts re-discovered
|
| MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. | Not applicable | No MCP servers in this loadout |
| Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode, instructions hidden in HTML comments or after long blank padding, or prompt-injection and covert wording (such as telling the agent to keep something from you), and the listing's network flag is honest. | Pass | Clean: no credentials, risky shell, hidden text or covert instructions
|
| ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. | Pass | 2 references between files resolve for every agent |
| Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. | Pass | 11 files, 22.5 KB total; largest skills/mv3-worker-guard/scripts/mv3_worker_guard.py 8.3 KB
|
| LicenceA recognised SPDX licence, with the licence text available. | Pass | MIT, by SetupBay; text in the repository LICENSE |
| Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. | Pass | mv3_worker_guard.py passes an MV3 counter extension whose state lives in chrome.storage.session, and flags every MV2 line in the skill's sample: 14 removed APIs and things a worker can't do as errors, plus globals, a long timer and a nested listener as warnings. In Playwright's Chromium (@playwright
|
What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results from agent runs are separate and stay untested until an agent actually runs.
Install
Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.
Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o CLAUDE.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/CLAUDE.md curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .claude/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
CLAUDE.md | CLAUDE.md |
skills/mv3-worker-guard/SKILL.md | .claude/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .claude/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .claude/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .claude/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .claude/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .claude/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Claude Code reads.
Codex CLI reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/mv3-worker-guard/SKILL.md | .agents/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Codex CLI reads.
Cursor reads AGENTS.md + .cursor/rules/*.mdc and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .cursor/rules/mv3-service-worker.mdc https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/.cursor/rules/mv3-service-worker.mdc curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
.cursor/rules/mv3-service-worker.mdc | .cursor/rules/mv3-service-worker.mdc |
skills/mv3-worker-guard/SKILL.md | .agents/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Cursor reads.
Gemini CLI reads GEMINI.md (imports AGENTS.md with @./AGENTS.md) and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o GEMINI.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/GEMINI.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
GEMINI.md | GEMINI.md |
skills/mv3-worker-guard/SKILL.md | .agents/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Gemini CLI reads.
Antigravity CLI reads AGENTS.md (read natively; GEMINI.md is also read) and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/mv3-worker-guard/SKILL.md | .agents/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Antigravity CLI reads.
GitHub Copilot reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/SKILL.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/scripts/mv3_worker_guard.py curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-to-mv3.md curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/worker-restart.spec.mjs curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/background.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt curl -fsSL --create-dirs -o .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt https://setupbay.com/raw/chrome-mv3-service-worker-guard/1.0.0/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/mv3-worker-guard/SKILL.md | .agents/skills/mv3-worker-guard/SKILL.md |
skills/mv3-worker-guard/scripts/mv3_worker_guard.py | .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py |
skills/mv3-worker-guard/references/mv2-to-mv3.md | .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md |
skills/mv3-worker-guard/references/worker-restart.spec.mjs | .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs |
skills/mv3-worker-guard/references/mv2-sample/background.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt |
skills/mv3-worker-guard/references/mv2-sample/popup.js.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt |
skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt | .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what GitHub Copilot reads.
Or as a Claude Code plugin
Adds the skill to Claude Code for all your projects, without copying files. Plugins can't carry project rules, so add AGENTS.md and CLAUDE.md with the commands above. About the plugin marketplace.
/plugin marketplace add https://setupbay.com/marketplace.json /plugin install chrome-mv3-service-worker-guard@setupbay
Files
Rules AGENTS.md Service worker facts, MV2 to MV3 replacements and the done checklist. 32 lines
# AGENTS.md: Chrome extension, Manifest V3 service worker guard
This extension is **Manifest V3**. Its background script is a **service worker**, not a background page. Chrome
starts it for an event and stops it after about 30 seconds without events. Manifest V2 no longer runs in
Chrome, but much of the extension code you learnt from is MV2.
## What a service worker is not
- It has no DOM: no `document`, `window`, `localStorage`, `sessionStorage`, `XMLHttpRequest` or `alert()`. Use
`chrome.storage`, `fetch`, and an offscreen document (`chrome.offscreen`) for work that needs a DOM.
- It doesn't keep globals. A `let count = 0` at the top of the worker goes back to `0` every time Chrome stops and
restarts it. Keep state in `chrome.storage.session` (in memory, cleared when the browser closes) or
`chrome.storage.local`.
- It doesn't keep timers. Use `chrome.alarms` for anything that should happen later than a few seconds from now.
- Chrome wakes it only for listeners it already knows about. Register every `chrome.*.on*.addListener` call
synchronously at the top level of the worker, not inside a callback, a function or after an `await`.
- There is no background page to reach into: `chrome.extension.getBackgroundPage()` and
`chrome.runtime.getBackgroundPage()` don't exist in MV3. Send messages.
## API replacements
- `chrome.browserAction` / `chrome.pageAction` → `chrome.action`
- `chrome.tabs.executeScript` / `insertCSS` → `chrome.scripting.executeScript` / `insertCSS` (permission `scripting`)
- `chrome.extension.sendRequest` / `onRequest` / `getURL` → `chrome.runtime.sendMessage` / `onMessage` / `getURL`
- `chrome.tabs.getSelected` → `chrome.tabs.query({ active: true, currentWindow: true })`
- blocking `webRequest` → `chrome.declarativeNetRequest` rules
- No `eval`, `new Function` or remotely hosted scripts: everything runs from the package.
## Before you say "done"
1. Run the `mv3-worker-guard` scanner on the extension source and `manifest.json`. It must report `0 error(s)`; any
warning you keep needs a `// guard: ok <reason>` comment.
2. Load the extension, stop its service worker (chrome://serviceworker-internals, or the CDP command
`ServiceWorker.stopAllWorkers` in a test), then use the feature again. It must behave the same. Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
# CLAUDE.md
@AGENTS.md
## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `mv3-worker-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From SetupBay "Chrome MV3 service worker guard for MV2 code" v1.0.0 — https://setupbay.com/loadouts/chrome-mv3-service-worker-guard/ --> Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 11 lines
# GEMINI.md
@./AGENTS.md
## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `mv3-worker-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From SetupBay "Chrome MV3 service worker guard for MV2 code" v1.0.0 — https://setupbay.com/loadouts/chrome-mv3-service-worker-guard/ --> Cursor rule .cursor/rules/mv3-service-worker.mdc Never/always table for background scripts and manifest.json. 23 lines
---
description: Manifest V3 service worker rules; no MV2 background-page code
globs: "**/sw.js,**/sw.ts,**/service-worker.*,**/service_worker.*,**/background.*,**/*-worker.*,**/manifest.json"
alwaysApply: false
---
The background script is an MV3 service worker. Chrome stops it after about 30 seconds idle.
| Never | Always |
|---|---|
| `let count = 0` as state | `chrome.storage.session` / `chrome.storage.local` |
| `setTimeout(fn, 60 * 60 * 1000)` | `chrome.alarms.create("name", { periodInMinutes: 60 })` |
| `addListener` inside a callback or after `await` | every `addListener` at the top level, synchronously |
| `document`, `window`, `localStorage`, `XMLHttpRequest` | `fetch`, `chrome.storage`, `chrome.offscreen` for DOM work |
| `chrome.browserAction`, `chrome.pageAction` | `chrome.action` |
| `chrome.tabs.executeScript` | `chrome.scripting.executeScript` |
| `chrome.extension.getBackgroundPage()` | `chrome.runtime.sendMessage` |
| `webRequest` with `["blocking"]` | `chrome.declarativeNetRequest` |
| `eval`, `new Function`, remote `<script src>` | code shipped in the package |
- An async `onMessage` reply needs `return true` from the listener.
- After editing, run the `mv3-worker-guard` skill's scanner, then restart the worker and use the feature again. Skill skills/mv3-worker-guard/SKILL.md Scan for MV2 APIs and lifecycle bugs, then restart the worker and check the state survived. 45 lines
---
name: mv3-worker-guard
description: Check a Chrome extension's Manifest V3 service worker and scripts for MV2 APIs (browserAction, tabs.executeScript, getBackgroundPage, blocking webRequest) and for code that breaks when Chrome stops the worker (globals as state, long timers, nested listeners, DOM and localStorage), then prove state survives a worker restart. Use after writing or editing extension background code, when a feature works at first and then stops, or when porting an MV2 extension.
---
# MV3 service worker guard
Chrome stops an MV3 service worker after about 30 seconds without events and starts it again for the next one.
Code written for an MV2 background page assumes it runs forever, so it works while you click around and breaks
later: counts reset to zero, timers never fire, and `document` or `localStorage` throw. This skill finds those
patterns and the removed MV2 APIs, then checks the behaviour by restarting the worker.
## 1. Scan
```bash
python3 .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py src manifest.json
```
(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)
- The worker rules apply to files named like a background script (`sw.js`, `service-worker.js`, `background.js`,
`*-worker.js` and the `.ts` versions). Other files get the API rules only.
- `error` lines are MV2 APIs or things a worker can't do. Fix each with the replacement on the line; see
`references/mv2-to-mv3.md`.
- `warn` lines are lifecycle risks: a mutable global, a long timer, a listener registered below the top level,
a keep-alive hack. Fix them, or add `// guard: ok <reason>` (for example, a constant cache that's fine to rebuild).
## 2. Restart the worker and use the feature again
The bug only shows after a restart, so test that directly. With Playwright and a persistent context that loads the
unpacked extension, stop every service worker through CDP and send the next message:
```js
const cdp = await context.newCDPSession(page); // page: an extension page such as popup.html
await cdp.send("ServiceWorker.enable");
const stopped = new Promise((resolve) => cdp.on("ServiceWorker.workerVersionUpdated", (e) => {
if (e.versions.some((v) => v.runningStatus === "stopped")) resolve();
}));
await cdp.send("ServiceWorker.stopAllWorkers");
await stopped;
// now sendMessage again and assert the state is unchanged
```
`references/worker-restart.spec.mjs` is a complete test that does this. By hand, use chrome://serviceworker-internals
and click **Stop**.
## 3. Report
Say which MV2 APIs you replaced, where state now lives (`storage.session` or `storage.local`), which timers became
alarms, and that the scanner was clean and the feature still worked after a worker restart. Skill file skills/mv3-worker-guard/scripts/mv3_worker_guard.py Read-only scanner for MV2 APIs, worker-only problems and lifecycle risks. 146 lines
#!/usr/bin/env python3
"""Find Manifest V2 extension APIs, and code that breaks in an MV3 service worker, in a Chrome extension's source.
Usage: python3 mv3_worker_guard.py [PATH ...] (default: the current folder)
python3 mv3_worker_guard.py --list (print every rule)
A "guard: ok" comment on a line silences its warnings (never its errors).
Prints one line per finding: path:line: level [rule] what is wrong -> what to use
Exit 0 when nothing at "error" level is found, 1 when something is, 2 on bad usage.
Standard library only. Read-only: it never edits a file and makes no network calls.
"""
import fnmatch
import os
import re
import sys
# (rule id, level, regex, message, file patterns). Patterns run against each line with comments removed.
RULES = [
('browser-action', 'error', '\\bchrome\\.(browserAction|pageAction)\\b',
'chrome.browserAction/pageAction were removed in MV3 -> chrome.action', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('tabs-execute-script', 'error', '\\bchrome\\.tabs\\.(executeScript|insertCSS|removeCSS)\\(',
'tabs.executeScript/insertCSS/removeCSS were removed in MV3 -> chrome.scripting.executeScript/insertCSS/removeCSS (permission scripting)', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('extension-legacy', 'error', '\\bchrome\\.extension\\.(getBackgroundPage|getViews|getURL|sendRequest|onRequest|connect|sendMessage|onMessage|getExtensionTabs)\\b',
'chrome.extension.* messaging and page access are gone in MV3 -> chrome.runtime.sendMessage/onMessage/getURL; there is no background page', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('runtime-background-page', 'error', '\\bchrome\\.runtime\\.getBackgroundPage\\(',
'MV3 has no background page -> message the service worker with chrome.runtime.sendMessage', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('tabs-legacy', 'error', '\\bchrome\\.tabs\\.(getSelected|getAllInWindow|sendRequest|onActiveChanged|onSelectionChanged)\\b',
'removed tabs API -> chrome.tabs.query({ active: true, currentWindow: true }) / tabs.sendMessage / tabs.onActivated', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('webrequest-blocking', 'error', '[\\"\']blocking[\\"\']',
'blocking webRequest is not available to MV3 extensions (except force-installed) -> chrome.declarativeNetRequest rules', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('remote-code', 'error', '\\beval\\(|\\bnew Function\\(|setTimeout\\(\\s*[\\"\'`]',
'MV3 forbids running strings as code -> ship the code in the package', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('remote-script-tag', 'error', '\\.src\\s*=\\s*[\\"\'`]https?://|import\\(\\s*[\\"\'`]https?://',
'MV3 forbids remotely hosted code -> bundle the script', ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx']),
('sw-dom', 'error', '\\b(document|window)\\.|\\blocalStorage\\b|\\bsessionStorage\\b|\\bXMLHttpRequest\\b|\\balert\\(',
'service workers have no DOM, window, localStorage or XMLHttpRequest -> chrome.storage, fetch, and an offscreen document for DOM work', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),
('sw-global-state', 'warn', '^(let|var)\\s+\\w+\\s*=',
'a mutable global in the service worker is lost when Chrome stops it (about 30 s idle) -> keep state in chrome.storage.session or chrome.storage.local', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),
('sw-long-timer', 'warn', '\\bset(Timeout|Interval)\\([^;]*,\\s*(\\d{5,}|\\d+\\s*\\*\\s*60\\b)|^\\s*\\}\\s*,\\s*(\\d{5,}|\\d+\\s*\\*\\s*60\\b)',
'timers die with the worker -> chrome.alarms for anything longer than a few seconds', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),
('sw-nested-listener', 'warn', '^\\s+chrome\\.[\\w.]+\\.on[A-Z]\\w*\\.addListener\\(',
'register event listeners synchronously at the top level of the worker, not inside a function or after an await, so Chrome can wake the worker for them', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),
('sw-keepalive', 'warn', 'keep-?alive|setInterval\\([^,]*chrome\\.runtime\\.getPlatformInfo',
'keep-alive hacks fight the worker lifecycle -> store state and let the worker stop', ['sw.js', 'sw.ts', 'service-worker.js', 'service-worker.ts', 'service_worker.js', 'service_worker.ts', 'background.js', 'background.ts', '*-worker.js', '*-worker.ts', '*_worker.js', '*_worker.ts']),
('mf-version-2', 'error', '\\"manifest_version\\"\\s*:\\s*2\\b',
'Manifest V2 no longer runs in Chrome -> "manifest_version": 3', ['manifest.json']),
('mf-browser-action', 'error', '\\"(browser_action|page_action)\\"\\s*:',
'browser_action/page_action -> "action"', ['manifest.json']),
('mf-background-page', 'error', '\\"(scripts|page|persistent)\\"\\s*:',
'MV3 background is a service worker -> "background": { "service_worker": "sw.js", "type": "module" }', ['manifest.json']),
('mf-webrequest-blocking', 'error', '\\"webRequestBlocking\\"',
'webRequestBlocking is MV2 only -> declarativeNetRequest', ['manifest.json']),
]
FILES = ['*.js', '*.mjs', '*.ts', '*.tsx', '*.jsx', 'manifest.json']
COMMENT = '//'
SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__", ".agents", ".claude", ".cursor", "dist", "build", "target", ".godot"}
COMPILED = [(rid, lvl, re.compile(rx), msg, globs) for rid, lvl, rx, msg, globs in RULES]
def strip_comment(line):
"""Drop a trailing comment, leaving string literals alone."""
q = None
i = 0
while i < len(line):
c = line[i]
if q:
if c == "\\":
i += 2
continue
if c == q:
q = None
elif c in "\"'`":
q = c
elif line.startswith(COMMENT, i):
return line[:i]
i += 1
return line
def wanted(name):
return any(fnmatch.fnmatch(name, g) for g in FILES)
def walk(paths):
for p in paths:
if os.path.isfile(p):
yield p
continue
for root, dirs, files in os.walk(p):
dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)
for f in sorted(files):
if wanted(f):
yield os.path.join(root, f)
def scan(path):
out = []
name = os.path.basename(path)
try:
with open(path, encoding="utf-8", errors="replace") as fh:
lines = fh.read().splitlines()
except OSError as e:
return [(path, 0, "error", "unreadable", str(e))]
for n, raw in enumerate(lines, 1):
code = strip_comment(raw)
if not code.strip():
continue
accepted = "guard: ok" in raw # a person accepted the warnings on this line
for rid, lvl, rx, msg, globs in COMPILED:
if accepted and lvl == "warn":
continue
if globs and not any(fnmatch.fnmatch(name, g) for g in globs):
continue
if rx.search(code):
out.append((path, n, lvl, rid, msg))
return out
def main(argv):
if "--list" in argv:
for rid, lvl, _, msg, globs in RULES:
print(f"{lvl:5} [{rid}] {msg}" + (f" (only {', '.join(globs)})" if globs else ""))
return 0
if any(a.startswith("-") for a in argv):
print(__doc__)
return 2
paths = argv or ["."]
for p in paths:
if not os.path.exists(p):
print(f"no such path: {p}", file=sys.stderr)
return 2
files = list(walk(paths))
findings = []
for f in files:
findings.extend(scan(f))
for path, n, lvl, rid, msg in findings:
print(f"{os.path.relpath(path)}:{n}: {lvl} [{rid}] {msg}")
errors = sum(1 for x in findings if x[2] == "error")
warns = sum(1 for x in findings if x[2] == "warn")
print(f"mv3_worker_guard: {len(files)} file(s), {errors} error(s), {warns} warning(s)")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:])) Skill file skills/mv3-worker-guard/references/mv2-to-mv3.md MV2 to MV3 table and the service worker lifecycle facts. 30 lines
# MV2 background page to MV3 service worker
From the Chrome docs: "Migrate to a service worker" and "Replace blocking web request listeners"
(https://developer.chrome.com/docs/extensions/develop/migrate). Checked in Playwright's Chromium (@playwright/test 1.64.1).
| MV2 | MV3 |
|---|---|
| `"background": { "scripts": [...], "persistent": false }` | `"background": { "service_worker": "sw.js", "type": "module" }` |
| `"browser_action"` / `"page_action"` | `"action"` |
| `chrome.browserAction.*` / `chrome.pageAction.*` | `chrome.action.*` |
| `chrome.tabs.executeScript(tabId, { code })` | `chrome.scripting.executeScript({ target: { tabId }, func })` |
| `chrome.tabs.insertCSS` / `removeCSS` | `chrome.scripting.insertCSS` / `removeCSS` |
| `chrome.extension.getBackgroundPage()` / `getViews()` | message the worker: `chrome.runtime.sendMessage` |
| `chrome.extension.sendRequest` / `onRequest` | `chrome.runtime.sendMessage` / `onMessage` |
| `chrome.extension.getURL` | `chrome.runtime.getURL` |
| `chrome.tabs.getSelected(null, cb)` | `chrome.tabs.query({ active: true, currentWindow: true })` |
| `webRequest.onBeforeRequest` with `["blocking"]` + `webRequestBlocking` | `declarativeNetRequest` rules (static `rule_resources` or `updateDynamicRules`) |
| `localStorage` in the background | `chrome.storage.local` (or `storage.session` for in-memory state) |
| globals as state | `chrome.storage.session` |
| `setTimeout` / `setInterval` for minutes or hours | `chrome.alarms` |
| `XMLHttpRequest` | `fetch` |
| DOM work in the background (`DOMParser`, audio, clipboard) | `chrome.offscreen.createDocument({ url, reasons, justification })` |
| `eval`, `new Function`, remote scripts | not allowed; ship the code in the package |
## Service worker lifecycle facts
- Chrome stops the worker after 30 seconds without events; a single request taking longer than 5 minutes is also stopped.
- Events reset the idle timer, and extension API calls do too.
- Register listeners at the top level, synchronously, so they exist when Chrome restarts the worker for an event.
- `chrome.storage.session` keeps data in memory for the browser session (about 10 MB) and survives worker restarts. Skill file skills/mv3-worker-guard/references/worker-restart.spec.mjs Playwright test that stops the service worker through CDP and checks state survives. 88 lines
// Stop the MV3 service worker the way Chrome does when it's idle, then talk to it again.
import { test as base, chromium, expect } from "@playwright/test";
import path from "node:path";
const withExtension = (dir) => {
const EXT = path.resolve(dir);
return base.extend({
context: async ({}, use) => {
const context = await chromium.launchPersistentContext("", {
channel: "chromium",
args: [`--disable-extensions-except=${EXT}`, `--load-extension=${EXT}`],
});
await use(context);
await context.close();
},
serviceWorker: async ({ context }, use) => {
let [worker] = context.serviceWorkers();
if (!worker) worker = await context.waitForEvent("serviceworker");
await use(worker);
},
extensionId: async ({ serviceWorker }, use) => use(serviceWorker.url().split("/")[2]),
});
};
async function countAcrossRestart(context, extensionId) {
const page = await context.newPage();
await page.goto(`chrome-extension://${extensionId}/popup.html`);
const send = (type) => page.evaluate((t) => chrome.runtime.sendMessage({ type: t }), type);
await send("count");
await send("count");
const before = (await send("get")).count;
const cdp = await context.newCDPSession(page);
await cdp.send("ServiceWorker.enable");
const stopped = new Promise((resolve) =>
cdp.on("ServiceWorker.workerVersionUpdated", (e) => {
if (e.versions.some((v) => v.runningStatus === "stopped")) resolve();
}),
);
await cdp.send("ServiceWorker.stopAllWorkers");
await stopped;
const after = (await send("get")).count;
return { before, after };
}
const good = withExtension("extension");
const globals = withExtension("globals-extension");
good("the MV3 worker has no DOM, localStorage, XMLHttpRequest or MV2 APIs", async ({ serviceWorker }) => {
const seen = await serviceWorker.evaluate(() => ({
document: typeof document,
localStorage: typeof localStorage,
XMLHttpRequest: typeof XMLHttpRequest,
browserAction: typeof chrome.browserAction,
executeScript: typeof chrome.tabs.executeScript,
getBackgroundPage: typeof chrome.extension?.getBackgroundPage,
action: typeof chrome.action,
alarms: typeof chrome.alarms,
}));
console.log(`worker globals ${JSON.stringify(seen)}`);
expect(seen).toEqual({
document: "undefined", localStorage: "undefined", XMLHttpRequest: "undefined",
browserAction: "undefined", executeScript: "undefined", getBackgroundPage: "undefined",
action: "object", alarms: "object",
});
});
good("state in chrome.storage.session survives a worker restart", async ({ context, extensionId }) => {
const { before, after } = await countAcrossRestart(context, extensionId);
console.log(`storage.session before=${before} after=${after}`);
expect(before).toBe(2);
expect(after).toBe(2);
});
good("the alarm is registered with chrome.alarms, not a timer", async ({ serviceWorker }) => {
// onInstalled creates the alarm asynchronously, so wait for it rather than racing it.
await expect.poll(() => serviceWorker.evaluate(async () => Boolean(await chrome.alarms.get("tidy")))).toBe(true);
const alarm = await serviceWorker.evaluate(() => chrome.alarms.get("tidy"));
console.log(`alarm ${alarm.name} every ${alarm.periodInMinutes} min`);
expect(alarm.periodInMinutes).toBe(30);
});
globals("state in a global is lost when Chrome stops the worker", async ({ context, extensionId }) => {
const { before, after } = await countAcrossRestart(context, extensionId);
console.log(`global before=${before} after=${after}`);
expect(before).toBe(2);
expect(after).toBe(0);
}); Skill file skills/mv3-worker-guard/references/mv2-sample/background.js.txt MV2 background page for checking the guard. 35 lines
// MV2 background page for a tab counter, used to check the guard.
var count = 0;
var blocked = ["ads.example.com"];
chrome.browserAction.onClicked.addListener(function (tab) {
count++;
chrome.browserAction.setBadgeText({ text: String(count) });
chrome.tabs.executeScript(tab.id, { code: "document.body.style.border = '4px solid red'" });
localStorage.setItem("count", count);
});
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
return { cancel: blocked.some((d) => details.url.includes(d)) };
},
{ urls: ["<all_urls>"] },
["blocking"]
);
chrome.storage.local.get("settings", function (s) {
chrome.runtime.onMessage.addListener(function (msg, sender, reply) {
reply({ count: count, settings: s.settings });
});
});
setInterval(function () {
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://example.com/rules.json");
xhr.send();
}, 60 * 60 * 1000);
chrome.tabs.getSelected(null, function (tab) {
console.log(tab.url, window.navigator.userAgent);
}); Skill file skills/mv3-worker-guard/references/mv2-sample/popup.js.txt MV2 popup that reaches into the background page. 4 lines
Skill file skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt MV2 manifest for checking the guard. 9 lines
All files: source zip · Licence MIT · By SetupBay · First listed
Credits
Public work that inspired this loadout. The wording here is our own; follow the links for the originals.
- Chrome docs: migrate to a service worker
- Chrome docs: the extension service worker lifecycle
- Chrome docs: replace blocking web request listeners
- Playwright docs: Chrome extensions
- Chrome DevTools Protocol: ServiceWorker domain
- @unblckhq: ask the agent for MV3 and declarativeNetRequest, not blocking webRequest
Installing and checks
How do I install Chrome MV3 service worker guard for MV2 code in Claude Code?
From your project root, put AGENTS.md, CLAUDE.md, skills/mv3-worker-guard/SKILL.md at .claude/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .claude/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .claude/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .claude/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .claude/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .claude/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .claude/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "Claude Code" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Chrome MV3 service worker guard for MV2 code in Codex CLI?
From your project root, put AGENTS.md, skills/mv3-worker-guard/SKILL.md at .agents/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "Codex CLI" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Chrome MV3 service worker guard for MV2 code in Cursor?
From your project root, put AGENTS.md, .cursor/rules/mv3-service-worker.mdc, skills/mv3-worker-guard/SKILL.md at .agents/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "Cursor" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Chrome MV3 service worker guard for MV2 code in Gemini CLI?
From your project root, put AGENTS.md, GEMINI.md, skills/mv3-worker-guard/SKILL.md at .agents/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "Gemini CLI" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Chrome MV3 service worker guard for MV2 code in Antigravity CLI?
From your project root, put AGENTS.md, skills/mv3-worker-guard/SKILL.md at .agents/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "Antigravity CLI" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Chrome MV3 service worker guard for MV2 code in GitHub Copilot?
From your project root, put AGENTS.md, skills/mv3-worker-guard/SKILL.md at .agents/skills/mv3-worker-guard/SKILL.md, skills/mv3-worker-guard/scripts/mv3_worker_guard.py at .agents/skills/mv3-worker-guard/scripts/mv3_worker_guard.py, skills/mv3-worker-guard/references/mv2-to-mv3.md at .agents/skills/mv3-worker-guard/references/mv2-to-mv3.md, skills/mv3-worker-guard/references/worker-restart.spec.mjs at .agents/skills/mv3-worker-guard/references/worker-restart.spec.mjs, skills/mv3-worker-guard/references/mv2-sample/background.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/background.js.txt, skills/mv3-worker-guard/references/mv2-sample/popup.js.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/popup.js.txt, skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt at .agents/skills/mv3-worker-guard/references/mv2-sample/manifest.json.txt. The "GitHub Copilot" tab on this page has the curl commands, or download https://setupbay.com/download/chrome-mv3-service-worker-guard/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
What has been checked in Chrome MV3 service worker guard for MV2 code?
It passed the Level 1 automated install checks on 2026-10-29: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.
More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.