What the agent does
The agent starts from the pull request, not from a generic React checklist. In an app shaped like cypress-io/cypress-realworld-app, package.json pins react and react-dom 18.2.0, react-router-dom 5.3.4, @mui/material 5.15, formik 2.4.6 and xstate 4.38.3, with Vite 8, TypeScript 5.8.3, Cypress 16 and Vitest 3 in development; engines asks for Node 22 or 24 and .nvmrc says 22.20.0, with a yarn.lock. yarn start:ci runs Vite on port 3000 and the Express backend in backend/app.ts on 3001, as .env sets through PORT and VITE_BACKEND_PORT, and cypress.config.ts points baseUrl at 3000, seeds the database through a db:seed task that POSTs to /testData/seed on 3001, and runs component tests from src/**/*.cy.tsx. .env also sets SEED_DEFAULT_USER_PASSWORD=s3cret, and the list:dev:users script prints the seeded usernames from data/database.json, which is how the agent signs in.
It reads what the repository already does for accessibility, so the review adds rather than repeats. eslint.config.mjs loads eslint-plugin-cypress only; there is no jsx-a11y plugin and no axe dependency in package.json. index.html sets lang="en". Across src/components the explicit ARIA is sparse: NavBar.tsx labels the drawer button open drawer, NotificationListItem.tsx labels its button mark as read, and TransactionListAmountRangeFilter.tsx ties its slider to range-slider with aria-labelledby. Forms such as SignInForm.tsx use MUI TextField with label props and data-test attributes, which gives inputs their names. TransactionItem.tsx renders MUI Avatar components with a src and no alt for the sender and receiver, and the whole list item carries an onClick on a ListItem, both of which the agent checks against the rendered DOM rather than the source alone.
For the pull request under review the agent clones the repository at the pull request's branch, runs yarn install --frozen-lockfile on the Node version from .nvmrc, starts the app with yarn start:ci, waits for both ports the way the cypress-io/github-action step in .github/workflows/main.yml does, and seeds the database. It reads the diff to list the components and routes the change touches, signs in with a seeded user and the default password, and opens each affected screen in its own browser in the states the change can produce: empty, loading, with validation errors, signed in and signed out. On each it runs axe-core from the browser, with no dependency added to the repository, and walks the screen with Tab, Shift+Tab, Enter, Space and Escape, recording focus order, visible focus, and whether dialogs and drawers trap and return focus. It then checks out the base branch and repeats the same screens, so every finding is a regression or a pre-existing problem, labelled as such.
The review is posted on the pull request as one review with a comment per finding, on the changed line in the component that renders the element. Each comment names the element and component, the route and state, the rule or keyboard step that failed, how to reproduce it, and a fix in MUI terms, such as an alt on the Avatar, an aria-label on an icon button, or a role and key handler on a clickable ListItem. Pre-existing problems the change did not cause go in the summary, not on lines. The agent writes accessibility.md with the full axe output per screen and the keyboard log, and pushes nothing.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Review this React pull request for accessibility regressions only. Read package.json, .nvmrc, .env, cypress.config.ts, eslint.config.mjs, index.html and the changed components first. Install with yarn install --frozen-lockfile, start the app with yarn start:ci, wait for ports 3000 and 3001, and seed the database through the backend's testData endpoint. Sign in with a seeded user from data/database.json and the default password in .env. For each screen the diff touches, in every state the change can produce, run axe-core from your browser without adding a dependency, and walk the screen with the keyboard, recording focus order, visible focus and focus trapping in dialogs and drawers. Repeat the same screens on the base branch and mark each finding as a regression or pre-existing. Post one review with a comment per regression on the changed line, naming the element, component, route, state, failing rule or keyboard step, reproduction and an MUI-specific fix; put pre-existing problems in the summary. Write accessibility.md with the full axe output and keyboard log. Do not modify source or push commits.",
"resources": [
{
"url": "https://github.com/your-org/your-app/pull/212"
}
]
}- 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.
- 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.
- 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 and accessibility.md as an artifact on the session. The review has one comment per regression, each anchored to the changed line in the component, with the route and state in which it appears, the axe rule id or the keyboard step that failed, the steps to see it, and a fix written against the MUI components the app uses. The summary lists pre-existing problems found on the same screens with the base branch so they can become their own issues, and names the screens and states that were checked and passed. No commit is pushed; the agent's branch checkout is read-only work in its sandbox.
A reviewer reads the regressions first and reproduces one with the steps given, then decides whether the pre-existing list deserves a follow-up. accessibility.md holds the full axe output per screen and the keyboard log, so a disputed comment can be traced to the rule and the DOM node that raised it. The session's transcript shows the install, the two servers starting, the seeding call and every page visit, so a reader can see which states were reached.
Run it on every pull request that changes a component
An automation in the Project with a github trigger on pull_request.opened and pull_request.synchronize starts one run per push with this prompt; the trigger adds the pull request to the run after the prompt, so nothing in input needs filling in. Only pushes by an owner, member or collaborator fire it, never pull requests from forks. Each run works in its own sandbox and posts its own review.
To review only pull requests that touch src/components, a handler on your server receives GitHub's pull request webhook, reads the changed files, and sends POST /o/{org}/sessions with the pull request URL in resources for the ones that qualify, with a request_id, one UUID kept per head SHA, so a redelivered webhook returns the same run.
The use case this job follows, with its automation and trigger: Review every pull request.
Why run it on Rush?
An accessibility review needs the app running at the right commit, a browser, axe, a keyboard and the patience to visit every state twice, once on the branch and once on the base. The agent has all of that in its own sandbox and does the comparison for every pull request, so the review distinguishes what this change broke from what was already broken, which a static lint rule cannot do.
The review lands on the pull request where the discussion already is, with reproductions a developer can follow in a minute, and the full axe output stays in accessibility.md for anyone who wants the raw rule results. Several pull requests can be reviewed at once, each in its own sandbox.
Frequently asked questions
Does it add axe or an ESLint plugin to my repository?
No. The agent runs axe-core from its own browser against the running app and pushes nothing. eslint.config.mjs and package.json are unchanged. If you want eslint-plugin-jsx-a11y or cypress-axe in the repository, that is a separate run with a pull request.
How does it tell a regression from an existing problem?
It runs the same screens and states on the base branch after the pull request's branch and compares. A finding present only on the branch is a regression and gets a line comment; one present on both goes in the review summary as pre-existing, so the author is not asked to fix what they did not break.
Which states does it check?
The ones the diff can produce: empty lists, loading skeletons, validation errors on forms, and signed-in and signed-out views, following the routes the changed components render on. The transcript lists every page visit, and the review names the states that were checked so a reader knows what passed as well as what failed.
What does it need to sign in?
The seeded users. yarn start:ci seeds data/database.json, yarn list:dev:users prints their usernames, and .env sets SEED_DEFAULT_USER_PASSWORD=s3cret. The agent uses those, so no real credentials are involved and nothing reaches an auth provider; the dev:auth0, dev:okta and dev:cognito scripts are not used.