What the agent does
The agent reads the alert payload that follows the prompt, then opens the issue and its latest event through the Sentry connection: the exception class and message, the frames with file, line and function, and the tags the Symfony integration attaches, such as the route name, the request URL and method, and the user. For a Symfony app those frames point into src/ and the route tag names the controller action, which is enough to know where the test must go before any code is read.
Then it reads the application. In an app shaped like symfony/demo, composer.json requires PHP 8.4 or later, Symfony 8 packages (framework-bundle, security-bundle, form, validator, twig-bundle, ux-live-component), Doctrine ORM 3.5 with a SQLite database at data/database.sqlite, and in require-dev PHPUnit 11.5, dama/doctrine-test-bundle, phpstan/phpstan 2 with the Doctrine and Symfony extensions, PHP-CS-Fixer and symfony/browser-kit. phpunit.dist.xml forces APP_ENV=test, boots tests/bootstrap.php, enables the DAMA extension so every test runs in a rolled-back transaction, and sets failOnDeprecation, failOnNotice and failOnWarning, so a deprecation triggered by the fix fails the suite too. .env.test points DATABASE_URL at data/database_test.sqlite, which is committed, so vendor/bin/phpunit runs with no database server. phpstan.dist.neon is level 6 over bin, config, public, src and tests.
The code map is small. src/Controller/BlogController.php serves /blog with pagination at /blog/page/{page}, the feed at /blog/rss.xml, a post at /blog/posts/{slug:post}, the comment form at /blog/comment/{postSlug}/new behind IsGranted('IS_AUTHENTICATED'), and /blog/search; src/Controller/Admin/BlogController.php sits under /admin/post behind ROLE_ADMIN with edit and delete decided by src/Security/PostVoter.php, which grants only the post's author. src/Entity/Post.php validates title, a summary of at most 255 characters, content of at least 10 and at most four tags; src/Entity/Comment.php requires 5 to 10,000 characters and an IsTrue on isLegitComment. src/Repository/PostRepository.php filters publishedAt <= :now in findLatest and paginates through src/Pagination/Paginator.php with PAGE_SIZE 10. src/EventSubscriber/CommentNotificationSubscriber.php sends mail on CommentCreatedEvent. The agent matches the event's frame to one of these and reads the whole method.
The test comes first, in the style the suite already has. tests/Controller/BlogControllerTest.php shows the shape: static::createClient(), $client->request('GET', '/en/blog/'), loginUser with jane_admin from the fixtures, submitForm('Publish comment', [...]), and assertions on the crawler; tests/Controller/DefaultControllerTest.php adds data providers for public and secure URLs. The agent writes a case that sends the request the event recorded, with the same locale prefix, parameters and signed-in user, and confirms it fails with the event's exception. Then it changes the one method, voter, repository query or entity constraint at fault, and runs vendor/bin/phpunit, vendor/bin/phpstan analyse and vendor/bin/php-cs-fixer check --diff, plus the bin/console lint:container, lint:twig and doctrine:schema:validate --skip-sync steps from .github/workflows/lint.yaml when the fix touched configuration, templates or an entity.
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 Symfony application; its payload follows this prompt. Use the Sentry connection to read the issue and its latest event: the exception class and message, the frames with file and line, and the route, URL, method and user tags. Read composer.json, phpunit.dist.xml, .env.test and phpstan.dist.neon to learn how the suite runs, then find the controller action, voter, repository method or entity the frames point at under src/. Write a WebTestCase under tests/ in the style of the existing controller tests that sends the recorded request with the same locale, parameters and signed-in user, and confirm it fails with the event's exception. Fix the cause in that one place without catching the exception or widening a validation constraint to hide it. Run vendor/bin/phpunit, vendor/bin/phpstan analyse and vendor/bin/php-cs-fixer check --diff, and the lint:container, lint:twig and doctrine:schema:validate --skip-sync console commands when configuration, templates or an entity changed. Open a pull request that links the Sentry issue, quotes the message and frames and the failing test output, and explains the cause. If it does not reproduce, stop and report what you tried.\n\nAlert payload:\n{payload}",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
],
"connections": [
"sentry"
]
}- 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 with one new test case under tests/ and a change to one file under src/, or to a Twig template or a config/packages/ file when the frames point there. Its description links the Sentry issue, quotes the exception and the frames, shows the new case failing before the change and passing after, and gives the PHPUnit, PHPStan and PHP-CS-Fixer results, naming any console lint that also ran. composer.json and composer.lock do not change.
Before merging, a reviewer checks that the test exercises the route the event recorded rather than the method in isolation, since the bug may live in the firewall, the voter or the form rather than the controller; that a change to a validation constraint on Post or Comment is a real fix and not a relaxation; and that failOnDeprecation still passes, which the quoted PHPUnit output shows. If the frames come from a release older than the current code and the line has already moved, the description says so and the test pins the current behaviour.
Run it on every Sentry issue alert
Create an alert rule in the Sentry project for the application with a webhook action pointed at a handler on your server. Sentry signs its own requests and cannot add the Authorization: Bearer header a Rush webhook trigger requires, so the handler receives the alert, places its JSON after the prompt, and sends this request with the Sentry issue id as Idempotency-Key; a redelivered alert then returns the run already started instead of a second one.
Set a webhook_url on the API key and Rush POSTs run.completed with the session_url when the turn ends, so the handler can read the pull request link and post it on the Sentry issue. The key's max_concurrent limit bounds how many alerts are worked at once; the handler queues the rest.
The use case this job follows, with its automation and trigger: Fix a bug from an alert.
Why run it on Rush?
Reproducing a Symfony error means running the kernel in the test environment with the right firewall, fixtures and database, and that is exactly what a laptop mid-task does not have set up. The sandbox installs with Composer, uses the committed SQLite test database, and runs the one WebTestCase that matters before the whole suite, with every command and its output kept on the session.
The pull request carries its proof: the event the agent read, the case failing with that exception and passing after the change, and the PHPUnit, PHPStan and CS-Fixer output a reviewer can check line by line. Several alerts become several sandboxes, and the reviewer judges a small diff rather than a reconstruction of someone else's stack trace.
Frequently asked questions
Does the app need the Sentry bundle for this to work?
The event has to come from somewhere, so your application reports to Sentry with the Symfony SDK. The demo repository does not ship it; what the agent needs is the Sentry connection on the run, which reads the issue and its event. The pull request does not add or change Sentry configuration.
What does the test look like?
A WebTestCase next to the existing ones under tests/Controller/, using static::createClient(), the request the event recorded with its locale prefix and parameters, and loginUser with a fixture user when the route is behind the firewall. The DAMA extension rolls its database changes back, so the committed data/database_test.sqlite stays clean.
Will it loosen a validation constraint to make the error go away?
No. A constraint on Post or Comment is the application's rule, and the prompt forbids widening one to hide an exception. If the event shows that a constraint message is wrong or a form never reaches the validator, the fix goes to the form type or the controller, and the description explains the choice.
What about deprecations?
phpunit.dist.xml sets failOnDeprecation, so a fix that calls a deprecated Symfony or Doctrine method fails the suite, and the agent changes the call rather than the flag. The quoted PHPUnit output in the pull request shows the suite green under that setting.
What if the error is in a Twig template?
The frames then point at a compiled template under var/cache, and the agent maps that back to the source under templates/. The test still goes through the controller, the fix lands in the template, and bin/console lint:twig templates --env=prod runs as in the lint workflow.