What the agent does
The agent reads the alert payload after the prompt, then opens the issue and its latest event through the Sentry connection: the exception class and message, the request URL and method, the release and environment tags, and the in-app frames. On a Wagtail site the frames usually end in a page model method such as get_context, a view, or a template render, so the agent keeps the URL and the frame's file and line as the two facts it must reproduce.
It then reads how the site is built and tested. In wagtail/bakerydemo, requirements/base.txt pins wagtail>=8.0,<8.1 and Django>=6.0,<6.1, .python-version says 3.12, and bakerydemo/settings/base.py falls back to SQLite when DATABASE_URL is unset and uses wagtail.search.backends.database for search. bakerydemo/settings/test.py swaps in a local-memory cache, the MD5 password hasher and the immediate task backend, and .github/workflows/ci.yml runs ./manage.py check, ./manage.py makemigrations --check --noinput and ./manage.py test with DJANGO_SETTINGS_MODULE=bakerydemo.settings.test after uv pip install -r requirements/development.txt. The repository's AGENTS.md says render tests belong in bakerydemo/<app>/tests/, that focused page setup beats loading the full bakerydemo.json fixture, and that bakerydemo/search/views.py hard-codes the page models it searches. Only two test files exist, both under bakerydemo/base/tests/.
The open issues on this repository show where a Sentry event on a site like it tends to point. templates/base/form_page.html renders page.intro, a field FormPage in bakerydemo/base/models.py does not define, so a template that expects it is dead code or, with a stricter template setting, an error. BreadsIndexPage.paginate in bakerydemo/breads/models.py takes *args it never reads and calls self.get_breads() a second time, so /breads/ evaluates the queryset twice. The fallback branch of search() in bakerydemo/search/views.py concatenates ids from four per-model searches and refilters with Page.objects.live().filter(id__in=page_ids), which drops the merged order. BlogIndexPage.get_child_tags loops over every post and reads post.get_tags, which runs a tag query and a parent-page query per post. An exception event lands on the first kind; a performance issue with an N+1 fingerprint lands on the last.
The agent writes the reproduction as a WagtailPageTestCase in bakerydemo/<app>/tests/, building the tree in setUpTestData the way test_home_page.py does with Page.get_first_root_node() and a Site, publishing the page the URL names, and asserting the status code the route should return or, for a query-count issue, wrapping the request in assertNumQueries. It watches the test fail with the same message as the event, fixes the cause in the model, view or template, and runs env DJANGO_SETTINGS_MODULE=bakerydemo.settings.test ./manage.py test, ./manage.py makemigrations --check --noinput and make lint-server, which is ruff format --check, ruff check, curlylint and djhtml --check. Then it opens a pull request that links the Sentry issue, quotes the frames and shows the test before and after.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "A Sentry alert fired for this Wagtail site; the payload follows this prompt. Use the Sentry connection to read the issue, its latest event, the request URL, the tags and the in-app frames. Find the page model, view or template the frames point at. Write a WagtailPageTestCase in bakerydemo/<app>/tests/ that builds the page tree in setUpTestData, requests the same URL and asserts the response the route should give, or the query count for a performance issue; confirm it fails with the same error. Fix the cause in the model, view or template rather than hiding it in the template, add a migration if a field changes, run ./manage.py test with bakerydemo.settings.test, ./manage.py makemigrations --check and make lint-server, and open a pull request that links the Sentry issue, quotes the frames and the failing test output, and explains the cause. Do not change the demo fixture unless the fix needs it. If it does not reproduce, stop and report what you tried.\n\nAlert payload:\n{payload}",
"connections": [
"sentry"
],
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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 pull request that changes one app's models.py, the search view or a template, adds a test module under that app's tests/ directory, and adds a migration only when a field was added or renamed. The description links the Sentry issue, quotes the exception and the URL from the event, shows the new test failing before the change and passing after, and ends with the manage.py test summary, the makemigrations --check result and the lint result.
Before merging, a reviewer checks that the fix is in Python or in the right template rather than a {% if %} that hides the error, that a template change still renders the fields the model has, that a new migration is the one makemigrations would produce, and that bakerydemo.json is untouched unless the description says why. For a query-count fix the reviewer reads the assertNumQueries number and the query the agent replaced it with, for example a single Tag query over blog_blogpagetag__content_object__in instead of a loop.
Run it on every Sentry issue alert
Create an alert rule in the Sentry project for the site with a webhook action that points at a handler on your server. The handler sends this request with the alert's JSON after the prompt and the Sentry issue id as Idempotency-Key, so a redelivered alert returns the run already started instead of a second one. Sentry signs its own requests and cannot add the Authorization: Bearer header a Rush webhook trigger needs, which is why the handler sits in between.
Set a webhook URL on the API key and Rush POSTs run.completed with the session_url and the result when the turn ends; the handler can post the pull request link as a comment on the Sentry issue. An issue that keeps firing should not keep opening pull requests, so the handler keeps a map from issue id to pull request and skips issues that already have one.
The use case this job follows, with its automation and trigger: Fix a bug from an alert.
Why run it on Rush?
Reproducing a Wagtail error by hand means a virtualenv, a database, a page tree with a Site pointing at it, and the right settings module, before the first request is made. The agent does that in its own sandbox from requirements/development.txt, runs the one test that matters, and hands back a diff. Several alerts from the same site are several sandboxes at once, each with its own database file.
The pull request carries its own evidence. The transcript shows the Sentry event the agent read, the test failing with the same exception and passing after the change, and the exact manage.py test and make lint-server output, so the reviewer judges the fix rather than rebuilding the bug on a laptop.
Frequently asked questions
Does it need Postgres or Elasticsearch?
Not for this site. settings/base.py uses SQLite when DATABASE_URL is unset and the database search backend by default, so the test suite runs inside the sandbox. If the event's frames are in the Elasticsearch branch of search/views.py, the agent says so and reproduces the database branch only, or you give it a search service through a Project secret.
Will it load the demo fixture?
No, unless the error is in the fixture itself. AGENTS.md asks for focused page setup over load_initial_data, and the existing tests create a HomePage under the root in setUpTestData. The agent builds only the pages the URL in the event needs.
What if the error is in a template, like a missing field?
The test still requests the URL and asserts on the rendered response. The fix is either to add the field to the model with a migration or to remove the dead template block, and the pull request says which it chose and why. It does not wrap the block in another conditional to make the error go away.
How does it handle a performance issue rather than an exception?
A Sentry performance issue names the span and the N+1 pattern. The agent writes the test with assertNumQueries around the request, records the number before the change, replaces the per-object loop with a single query, and sets the assertion to the new count so the regression cannot come back.
What if the Sentry release is older than main?
The agent compares the release tag on the event with the current code at the frame's line. If the code already changed, the pull request says the issue is likely fixed, still adds the test to pin the current behavior, and asks the reviewer to resolve the issue in Sentry instead of inventing a second fix.