# Review Pull Requests on a Chrome Extension Project

Yes. An agent checks out the pull request, runs npm test and npm run build in its sandbox, reads the built manifest, and posts one review on permissions.

[Run this on Rush](https://cloud.getrush.ai/run/review-pull-requests/chrome-extension/), in a Project that has your repository.

## What the agent does

The agent reads the manifest first, because in a Manifest V3 extension the permissions and the contexts are the review. In chibat/chrome-extension-typescript-starter, `public/manifest.json` declares `manifest_version: 3`, an `action` with `popup.html` and `icon.png`, an `options_ui` page at `options.html`, one content script matching `<all_urls>` that loads `js/vendor.js` and `js/content_script.js`, a `background.service_worker` at `js/background.js`, `permissions: ["storage"]` and `host_permissions: ["<all_urls>"]`. Every API call in `src/` has to be covered by one of those entries, and every new entry widens what the store review and the install prompt show.

Then it reads how the build produces what Chrome loads. `webpack/webpack.common.js` has four entries, `popup`, `options`, `background` and `content_script`, writes them to `dist/js/[name].js`, splits shared code into a `vendor` chunk for every entry except `background`, compiles with `ts-loader`, and copies `public/` into `dist/` with `copy-webpack-plugin`. `webpack.prod.js` sets `mode: 'production'` and `webpack.dev.js` adds `inline-source-map`; `package.json` maps `build` to the production config and `watch` to the development one. `popup.html` and `options.html` each load `js/vendor.js` before their own script, which is why the vendor split must keep excluding `background`: a service worker cannot load a second script tag. `tsconfig.json` is strict with `jsx: react` and `moduleResolution: bundler`, and `@types/chrome` is pinned at `0.0.158`.

The checks the repository runs are small. `.github/workflows/build.yml` does `npm ci`, `npm run build --if-present` and `npm test` on Node 20 and 22. `jest.config.js` roots tests in `src` and transforms `.ts` with `ts-jest` under `tsconfig.test.json`, and the only test, `src/__tests__/sum.ts`, checks `sum(1, 1)`. Nothing in the suite touches the `chrome` namespace, so the agent runs `npm ci`, `npm run build` and `npm test`, then opens `dist/manifest.json` and the four bundles to confirm the build wrote what the manifest references, and writes a short Jest case with a stubbed `chrome` global in the sandbox when a finding needs proving, without adding it to the branch.

The diff is read with the two questions the prompt names. Permissions: `src/options.tsx` uses `chrome.storage.sync.get` and `set`, covered by `storage`; `src/popup.tsx` calls `chrome.action.setBadgeText`, `chrome.tabs.query({ active: true, currentWindow: true })` and `chrome.tabs.sendMessage`, which need no `tabs` permission for the fields it reads today, but reading `tabs[0].url` in the popup depends on `host_permissions` covering the page; a diff that adds `chrome.scripting`, `chrome.alarms` or `chrome.cookies` without a matching permission is blocking, and one that widens `host_permissions` is a question for the author. Message passing: `src/content_script.tsx` registers `chrome.runtime.onMessage.addListener` and calls `sendResponse` synchronously for both branches, so a change that makes the handler async must `return true` to keep the channel open; `src/background.ts` is a `polling` function that re-arms a 30-second `setTimeout`, which a Manifest V3 service worker will not keep alive once it is idle, so a diff that builds real work on that timer gets a finding pointing at `chrome.alarms` and the permission it would need. One GitHub review is posted per run, quoting `npm test` and `npm run build`, separating blocking issues from suggestions, with file and line on every finding, and nothing is pushed.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Review this pull request on a Manifest V3 Chrome extension. Check it out, run npm ci, npm run build and npm test, then open dist/manifest.json and the bundles under dist/js to confirm the build wrote every file the manifest references. Read the diff with public/manifest.json, webpack/webpack.common.js, src/background.ts, src/content_script.tsx, src/popup.tsx and src/options.tsx. Look for chrome.* APIs used without the permission or host permission that covers them, host_permissions or content_scripts matches widened without a reason in the description, onMessage listeners that respond asynchronously without returning true, work scheduled with setTimeout in the service worker instead of chrome.alarms, and webpack entries or vendor splitting changed so that background.js would load a second chunk. Confirm a finding with a short Jest case that stubs the chrome global in the sandbox where you can, but do not add it to the branch. Post one GitHub review that quotes the test and build results, separates blocking issues from suggestions, and cites file and line for every finding. Do not push commits.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/42"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a review on the pull request. The summary quotes the `npm test` result and the webpack output from `npm run build`, with the list of bundles written under `dist/js` and whether `dist/manifest.json` references each of them. Findings follow with file and line, marked blocking or suggestion: an API call with no covering permission names the key to add to `manifest.json`; a message handler that went async names the `return true` it needs.

The transcript keeps the install, the build output, the manifest the agent read back from `dist/`, and any stubbed-`chrome` test it wrote to confirm a finding, so a reviewer can see the exact assertion that failed rather than a description of it.

Before merging, a reviewer loads `dist/` as an unpacked extension, opens the popup and options page, and checks each cited line. A missing permission or a widened `host_permissions` is blocking by default; a suggestion to move the background timer to `chrome.alarms` is a follow-up unless the pull request depends on it.

## Run it on every pull request opened

Create an automation in the Project with a `github` trigger on `pull_request.opened` and `pull_request.synchronize`, `branch_patterns` set to `main`, and this prompt. The trigger adds the pull request after the prompt, so each pull request and each push to it gets its own sandbox, build and review, and a `read_only` automation can comment but not push.

The review is the result and appears on the pull request; the run is listed in the Project. `max_runs_per_hour`, 1 to 30, caps a burst of small pushes, and a fire past it is skipped as `rate_limited` until the next push. A manifest change is worth a second trigger on `pull_request.labeled` with a `permissions` label, so a maintainer can ask for a fresh review after the author answers the permission question.

## Why run it on Rush?

Extension review is mostly about what the manifest lets the code do, and a diff that touches `src/popup.tsx` says nothing about `public/manifest.json` or about what webpack writes to `dist/`. Checking it means building, reading the output manifest, and matching every `chrome.*` call against the permission list, which is tedious enough that it gets skipped. On Rush the agent does that in its own sandbox and keeps the build output and the manifest it read, so the review rests on the artifact Chrome would load.

One sandbox per pull request keeps reviews parallel and leaves the branch untouched; the author keeps their checkout, and the reviewer gets findings with the bundle list and the test output attached. The same run is cheap to repeat after the author narrows a host permission.

## Frequently asked questions

### The test suite only tests sum(). What does the agent get from running it?

Proof that `ts-jest` still compiles under `tsconfig.test.json` after the change, and a harness to write short cases into. For a message-passing finding the agent stubs `chrome.runtime.onMessage` and `chrome.tabs.sendMessage` on `globalThis` in a throwaway test, runs it in the sandbox, and quotes the result without adding the file to the branch.

### Does it install the extension in a browser?

No. The sandbox builds `dist/` and reads it back, which is what the repository's CI checks too. Loading `dist/` as an unpacked extension is the step a reviewer does before merging, and the review says which findings were read from the built output rather than observed in Chrome.

### What does it say about the 30-second polling in background.ts?

That a Manifest V3 service worker is stopped when idle, so the `setTimeout` loop in `src/background.ts` is not a reliable schedule. It is left alone in the starter, where the loop does nothing, and becomes a finding only when a pull request attaches real work to it; the finding names `chrome.alarms` and the `alarms` permission it would need.

### Which Node version runs the build?

`build.yml` tests Node 20 and 22, so the agent uses 22 and notes if the pull request adds a dependency whose engines field excludes 20. The `package.json` pins no engines of its own.
