# Fix Sentry Errors in a Laravel App

Yes. A Sentry alert starts a run; the agent reads the exception's frames, reproduces it with a Pest test in the Laravel app and opens a pull request with the fix.

[Run this on Rush](https://cloud.getrush.ai/run/fix-sentry-errors/laravel/), in a Project that has your repository.

## What the agent does

The run starts when your handler receives a Sentry alert and sends this request with the alert's JSON after the prompt. The agent takes the issue id from that payload and asks the Sentry connection for the issue and its latest event: the exception class and message, the request URL and method, the user and release tags, and the in-app frames with their file, function and line. In a Laravel app the frames name files under `app/`, so the agent has a starting point before it opens the clone.

In an app like laravelio/laravel.io it then orients itself from `composer.json`: PHP 8.4, `laravel/framework` ^11.5, Livewire 3, Filament 4 and Pest 3 with the Laravel and Livewire plugins. Routing is in `routes/web.php`, and controllers hand work to single-purpose job classes under `app/Jobs/` through `dispatchSync`, so a frame in `ReplyController::store` leads to `App\Jobs\CreateReply`, and one in `ThreadsController::markSolution` leads to `MarkThreadSolution` and then to `Thread::markSolution` in `app/Models/Thread.php`. That method throws `CouldNotMarkReplyAsSolution` when the reply's `replyAble()` is not a thread, and `ReplyController::redirectToReplyAble` aborts with a 500 for the same case, so those are the first lines the agent compares against the stack trace. `bootstrap/app.php` lists the exceptions the app does not report, which tells the agent whether the error should have reached Sentry at all.

Before it changes code the agent writes a failing test. The suite is Pest: `tests/Pest.php`, a `TestCase` with the `CreatesUsers` and `HttpAssertions` traits, `tests/Feature/*Test.php` for HTTP flows and `tests/Integration/Jobs/*Test.php` for jobs, each starting with `uses(TestCase::class)` and `uses(RefreshDatabase::class)`. It follows that layout: a feature test that posts the same route the Sentry event recorded, using `$this->login()` and the `Thread` and `Reply` factories the way `tests/Feature/ReplyTest.php` and `tests/Integration/Jobs/MarkThreadSolutionTest.php` do, with an assertion on the response the app should have given instead of the exception. It runs that file alone with `vendor/bin/pest tests/Feature/ThreadTest.php` and keeps the failure output for the pull request.

Then it fixes the cause in the job or model, not by catching the exception in the controller, and reruns until the test passes. `phpunit.xml` sets `DB_CONNECTION` to `mysql` and `tests.yml` runs `vendor/bin/pest -p` against a `mysql:5.7` service with `DB_DATABASE=laravel`, so the agent starts MySQL in its sandbox and runs the full suite the same way, after `composer install`, `npm ci` and `npm run build` as the workflow does. The `Coding Standards` workflow reuses Laravel's shared `laravel/.github` workflow, and `laravel/pint` is not a direct dependency in `composer.json`, so the agent runs Pint from a one-off install with Laravel's default preset on the files it touched, without adding it to the project.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Sentry alert fired for this Laravel app; its payload follows this prompt. Use the Sentry connection to read the issue, its latest event, the request URL and the in-app stack frames. Find the failing code by following the frames from the controller in app/Http/Controllers to the job class in app/Jobs and the model method it calls. Write a Pest test in the matching tests/Feature or tests/Integration/Jobs file, using the existing factories and the login helpers from tests/CreatesUsers.php, that sends the same request and fails with the same exception. Fix the cause in the job or model rather than catching the exception, then run vendor/bin/pest -p against MySQL the way .github/workflows/tests.yml does, and Pint with Laravel's default preset on the files you changed, without adding it to composer.json. Open a pull request that links the Sentry issue, quotes the exception and frames, and shows the test failing before the fix. Change nothing unrelated to this error. If you cannot reproduce it, open no pull request and say what you tried.\n\nAlert payload:\n{payload}",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ],
  "connections": [
    "sentry"
  ]
}
```

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 its own branch with two parts: the fix in a job class under `app/Jobs/` or a model under `app/Models/`, and a new Pest test that fails on the code before the fix. The description links the Sentry issue, quotes the exception line and the frames the fix addresses, shows the test output before and after, and names each command the agent ran. The repository's own `Tests` and `Coding Standards` workflows run on the branch as on any pull request.

The session keeps every step: the Sentry calls, each `pest` and `pint` command with its full output, the MySQL it started and the files it read. `GET /o/{org}/sessions/{id}/messages` returns that transcript, and the `run.completed` webhook carries the agent's summary in `data.result`, so your handler can post the pull request link back on the Sentry issue.

Before merging, a reviewer checks that the test reproduces the exception Sentry reported rather than a neighbour, that the fix changes behaviour in the job or model and not by wrapping the controller action in a `try`, and that a change to the thread or reply flow keeps the policy checks in `authorize` intact. If the fix is wrong, a follow-up message to the same session asks for a different one and the agent continues on the same branch.

## Run it on every Sentry issue alert

In Sentry, add an alert rule for the project whose action is a webhook, and point it at a small handler on your server. The handler sends this request with the alert's JSON after the prompt and the Sentry issue id as the `Idempotency-Key` header, so a resent alert returns the run it already started. One alert becomes one run and one pull request.

Set a webhook URL on the API key and Rush POSTs `run.completed` to it when the turn finishes, with the `session_url` and the result. Or have the handler call a `webhook` trigger on an automation in the Project with this prompt: it forwards the alert body with the trigger's secret as `Authorization: Bearer`, which Sentry cannot send itself. The body becomes the event text after the prompt, and `max_runs_per_hour` caps an issue that fires in bursts.

## Why run it on Rush?

A Laravel exception from production usually means a MySQL database, a Composer install, a built Vite bundle and an hour of reading before the first test runs. The agent does that setup in its own sandbox, with the same MySQL version and Pest flags the workflow uses, and nothing on your machine changes while it works.

The pull request carries its evidence: a failing test, a passing test and a transcript of every command. Several Sentry issues can each get their own sandbox at the same time, within the key's `max_concurrent` limit, so the errors that surface after a deploy are each answered with a proposed fix rather than queued behind one person.

## Frequently asked questions

### How does the agent reach my Sentry project?

Connect Sentry once with `POST /me/connections` as an MCP server and name it in the run's `connections` field, as the request on this page does. The run gets only the connections it names, and every call it makes is in the transcript. Tell it in the prompt if it should never resolve or comment on an issue.

### Does it need MySQL, or will SQLite do?

The app's `phpunit.xml` and `tests.yml` both run the suite on MySQL, so the agent starts MySQL in its sandbox and runs it the same way. Running on SQLite would risk passing a test that fails in CI on a MySQL-only query or collation.

### The frames point at a Livewire or Filament class. Does it still work?

Yes. The agent follows the frames to the component under `app/Livewire/` or `app/Filament/` and reproduces the error with Pest's Livewire plugin, which `composer.json` already requires, the way `tests/Feature/ReplyTest.php` tests the `EditReply` component.

### Will it add a global exception handler to make the error go away?

No. The prompt rules that out, and the diff would show any change to `bootstrap/app.php`. The fix has to change the job or model so the request succeeds or fails with a proper response, and the test has to prove it.

### What happens when it cannot reproduce the exception?

It opens no pull request. Its final message says which route, request body and account state it tried, what the event's breadcrumbs showed, and what it would need, such as a request field Sentry scrubbed. You can answer in a follow-up to the same session and it continues.
