# Add a Context Menu Item to a Chrome Extension from a Linear Issue

Yes. An agent reads the labeled issue, adds the contextMenus permission, a background handler and popup view, with tests, and opens a pull request.

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

## What the agent does

The agent starts from the issue text and the repository it names, not from a template. In a starter like chibat/chrome-extension-typescript-starter it opens `public/manifest.json` and finds a Manifest V3 extension whose `permissions` array holds only `storage`, whose `background` is the service worker `js/background.js`, and whose popup is `popup.html`. It checks `package.json` and finds `build` running `webpack --config webpack/webpack.prod.js`, `test` running `npx jest`, TypeScript pinned at `^5.0.4`, jest `^29.5.0` with `ts-jest`, and `@types/chrome` at `0.0.158`, so it can type the new chrome API calls without adding a dependency.

Then it wires the feature through the three files the starter already has. It adds `contextMenus` to the `permissions` array in `public/manifest.json`, the one line the browser requires before any menu call. In `src/background.ts`, which today only keeps the service worker alive with a `setTimeout` polling loop, it registers `chrome.contextMenus.create` with `contexts: ["selection"]` at install time and a `chrome.contextMenus.onClicked` listener that takes `info.selectionText`. To move the selection into `src/popup.tsx`, which is a React component rendered from `public/popup.html`, it writes the text with `chrome.storage.local`, since the `storage` permission is already granted and Manifest V3 service workers are ephemeral. The popup reads the stored value on open and shows it where the starter currently lists the current URL and time.

The agent tests what it can run headlessly. `jest.config.js` sets `roots: ["src"]` and transforms TypeScript through `ts-jest` with `tsconfig.test.json`, so it adds a test file under `src/__tests__` next to the starter's `sum.ts` test and mocks the `chrome` global (`contextMenus`, `storage`, `runtime`) the way `@types/chrome` types them. Because `tsconfig.json` sets `strict` and `noEmitOnError`, the build is also a type check: `npm run build` compiles all four webpack entries (`popup`, `options`, `background`, `content_script`) through `ts-loader` and fails on any type error, and the CopyPlugin step copies the edited `public/manifest.json` into `dist`. The agent runs `npm test` and `npm run build` in its sandbox before pushing, so a red check on the pull request is a surprise, not a habit.

When both commands pass, it commits on a new branch and opens a pull request whose description quotes the issue and links back to it. The repository's own `.github/workflows/build.yml` then runs on the pull request: `npm ci`, `npm run build --if-present` and `npm test` across Node 20.x and 22.x, so the PR shows green checks from the project's existing CI, not from a claim in the description.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Linear issue labeled agent says: \"Add a Context Menu Item to a Chrome Extension. Add a right-click menu item on selected text that sends the selection to the background worker and shows it in the popup, with the contextMenus permission in the manifest.\" Do the work in this repository, add tests for it, run npm test and npm run build, and open a pull request that links back to the issue.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

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 pull request on a new branch. In a repository shaped like chrome-extension-typescript-starter the diff is small and scoped: `public/manifest.json` gains one entry in `permissions`, `src/background.ts` gains the menu registration and the click listener that stores the selection, `src/popup.tsx` renders the stored text, and a new file under `src/__tests__` covers the handler. No new npm dependency appears, because `@types/chrome` already types the API and jest is already configured.

The pull request description links back to the Linear issue and says what it verified: that `npm test` and `npm run build` both passed in the sandbox before pushing, and that the project's own `build.yml` workflow ran on the pull request across Node 20.x and 22.x. The session's trajectory at `GET /o/{org}/sessions/{id}/messages` keeps every command the agent ran with its output, including the jest run and the webpack build, so a reviewer can replay the verification instead of trusting it.

Before merging, a reviewer checks that the only new permission is `contextMenus`, that the selection reaches the popup through `chrome.storage.local` rather than a long-lived service worker state that Manifest V3 would drop, that the tests mock the `chrome` global rather than calling a real browser, and that `dist/` artifacts were not committed.

## Run it on every Linear issue labeled agent

Set up a Linear automation that fires when an issue gets the `agent` label and have it call your server, which POSTs this same request to `POST /o/{org}/sessions` with the issue's repository in `resources`. Each labeled issue becomes its own run in its own sandbox with its own pull request, so triage in Linear is the only step a person does. The Rush GitHub App must be installed on the repository for `resources` to clone it, and the issue text has to carry enough detail for the agent to work from, since the run only sees what the label hands it.

Set a webhook URL on the API key and Rush POSTs `run.completed` or `run.failed` when each run's turn finishes, with the session URL, so your server can move the issue or post the pull request link where the team watches. Send an `Idempotency-Key` header on each start so a retry after a timeout does not open two runs for one label. A key runs at most `max_concurrent` sessions at once (4 by default) and answers `429` past that, so a burst of labeled issues should be queued rather than fired in parallel.

## Why run it on Rush?

Verifying a Chrome extension change on a laptop means loading `dist/` into a browser profile, right-clicking around, and remembering to re-run `npx jest` after each edit. On Rush the loop happens in the sandbox: the agent edits `public/manifest.json` and `src/background.ts`, runs `npm test` and `npm run build`, and the webpack build plus the strict `tsconfig.json` catch type errors before anything is pushed. Your browser, your loaded extension, and your working tree are not involved.

The result is reviewable instead of claimed. The pull request is small enough to read in one pass, its checks come from the repository's own `build.yml` on Node 20.x and 22.x, and the trajectory keeps every command with its output, so the reviewer sees the jest run and the webpack build, not a summary of them. And because each labeled issue is an independent run, five labeled issues become five branches and five pull requests at once, each waiting on a person to merge.

## Frequently asked questions

### Does the agent add any npm packages?

No. The starter already has `@types/chrome` at `0.0.158` for typing the `chrome` API, and jest with `ts-jest` is already in `devDependencies`. The diff stays limited to the manifest, the background worker, the popup, and a test file, so `package-lock.json` does not change.

### How does the selection survive the popup being closed?

Through `chrome.storage.local`. Manifest V3 service workers are torn down when idle, so the background listener writes the selection to storage, which the popup reads when it opens. The manifest already grants the `storage` permission, so no extra permission dialog is added for the data path.

### What do the tests actually run in a sandbox with no Chrome?

They mock the `chrome` global. The new test under `src/__tests__` stubs `contextMenus.create`, the `onClicked` listener, and `storage.local`, then calls the handler the way the browser would and asserts the selection is stored and read back. `jest.config.js` already points `roots` at `src` and transforms TypeScript with `tsconfig.test.json`, so no test setup changes are needed.

### Can it pick the issue up from Linear on its own?

The trigger is yours: a Linear automation on the `agent` label calls your server, which starts the run. The issue text becomes part of the input, and the pull request links back to the issue. Keep the label off issues that are not ready, because the run starts the moment the label lands.
