# Turn Sentry Errors Into Fix Pull Requests for an ASP.NET Core App

Yes. A Sentry alert starts a run; the agent follows the .NET frames into your controllers, reproduces the exception with an xUnit test and opens a pull request.

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

## What the agent does

An ASP.NET Core exception in Sentry has the exception type, the message, the HTTP route and a .NET stack trace whose in-app frames name a controller action, a command or a service method. The agent reads the alert payload after the prompt, fetches the issue and its latest event through the Sentry connection and keeps the frames, the route and the tags such as release and environment. Then it opens its clone and reads the project those frames belong to.

In an ASP.NET Core solution like bitwarden/server that is the first decision, because `bitwarden-server.slnx` holds many projects: `src/Api`, `src/Identity`, `src/Core`, `src/Admin`, `src/Billing`, `src/Events`, `src/Notifications` and the libraries under `src/Libraries`. `global.json` pins the SDK to 10.0.103 with `rollForward` set to `latestFeature`, and `Directory.Build.props` targets `net10.0`, turns `TreatWarningsAsErrors` on, enables nullable references and restores with a lock file. A frame in `Bit.Api.Vault.Controllers.CiphersController` points at `src/Api/Vault/Controllers/CiphersController.cs`, whose actions `Get`, `GetDetails`, `GetAll`, `Post`, `PostCreate`, `Put`, `PutPartial`, `PutShare`, `PutCollections` and `GetOrganizationCiphers` call `ICipherService` in `src/Core/Vault/Services/Implementations/CipherService.cs` and `ICipherRepository`. A frame in `Bit.Identity` points at `src/Identity/Controllers/AccountsController.cs`, with `PostRegisterSendVerificationEmail`, `PostRegisterFinish`, `PostPrelogin` and the WebAuthn assertion endpoints, or at the request validators under `src/Identity/IdentityServer/RequestValidators`. Exceptions the API means to return as 400, 404 or 409 are `BadRequestException`, `NotFoundException` and `ConflictException` from `src/Libraries/ExceptionHandling`; anything else reaching Sentry is a real 500.

The test goes in the matching test project. `test/Api.Test/Vault/Controllers/CiphersControllerTests.cs` uses `[Theory, BitAutoData]` with `SutProvider<CiphersController>` from `test/Common`, stubbing `GetDependency<ICipherRepository>().GetByIdAsync(...)` with NSubstitute, and `test/Identity.Test/Auth/Controllers/AccountsControllerTests.cs` does the same for Identity. `Directory.Build.props` sets xunit 2.6.6, NSubstitute 5.1.0 and AutoFixture.Xunit2 4.18.1 for every test project. The agent writes one test that stubs the dependencies to return what the Sentry breadcrumbs show, such as a cipher with a null `Data` field or a user with no master password, calls the action and asserts the exception or the response it should give. It runs `dotnet test test/Api.Test --filter <Name>` and keeps the failure as the reproduction.

Then it fixes the action or the service, reruns the test until it passes, and runs `dotnet test test/Api.Test` or `test/Identity.Test` in full, then `dotnet build` on the solution so a warning turned error elsewhere is caught. The `test.yml` workflow runs `dotnet test ./test --configuration Debug` across every project plus the `bitwarden_license/test` tree, which is long; the agent runs the affected projects and says in the pull request which it ran. It does not downgrade a thrown exception to a `BadRequestException` just to clear the alert when the real cause is a null the service should have handled.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Sentry alert fired for this ASP.NET Core app; its payload follows this prompt. Use the Sentry connection to read the issue, its latest event, the route, breadcrumbs and the in-app stack frames. Find the project and the action the frames name, starting with the vault controllers under src/Api/Vault/Controllers and the identity controllers and request validators under src/Identity. Add an xUnit test to the matching test project, using [Theory, BitAutoData] and SutProvider with NSubstitute stubs as the neighbouring tests do, that reproduces the exception. Fix the code until the test passes, run `dotnet test` for that test project and `dotnet build` for the solution with warnings as errors. Open a pull request that links the Sentry issue, quotes the frames and the failing test output, and explains the cause. Change nothing unrelated to this error.\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

The result is a pull request that changes one controller, service or validator and adds one `[Theory, BitAutoData]` test to the project that already tests it. The description links the Sentry issue, quotes the exception and the frames, shows the test failing before and passing after, and lists the `dotnet test` projects it ran with their summaries. Your own workflows run on the branch as they would on any pull request.

The session's transcript keeps every Sentry read, every `dotnet` command with its full output and every file opened, so a reviewer can check which test projects ran rather than take the description's word. The `run.completed` webhook carries the final message as `data.result` and a `session_url` for the rest.

Before merging, a reviewer checks that the stubs match the state the event showed, that a fix which now throws `NotFoundException` or `BadRequestException` is the honest status for that case, and that a change in `CipherService` keeps the organization and collection permission checks around it. A follow-up message to the same session asks for a different approach and the branch is amended.

## Run it on every Sentry issue alert

Create an alert rule in the Sentry project with a webhook action and point it at a handler on your server. The handler sends this request with the alert's JSON after the prompt and the issue id as `Idempotency-Key`, so Sentry's retries return the run already started rather than a second one. Each issue gets one run and one pull request.

Set a webhook URL on the API key and Rush POSTs `run.completed` to it with `session_url` when the turn finishes; the handler can comment the pull request link on the issue. Or have that handler call a `webhook` trigger on an automation in the Project with this prompt: it forwards the alert's JSON as the body with the trigger's secret as `Authorization: Bearer`, because Sentry signs its own webhooks and cannot send that header. The body becomes the event text after the prompt, and `max_runs_per_hour` caps an issue that keeps firing.

## Why run it on Rush?

A large .NET solution punishes a quick local fix: the restore, the build with warnings as errors, and a test tree that takes long to run in full. The agent does that in a sandbox with the pinned SDK, runs the one test first and the affected projects last, and keeps the output, so the reviewer opens a pull request whose claims are already backed by a transcript.

Each Sentry issue is its own run in its own clone, in parallel up to the key's `max_concurrent` limit, so an exception in the vault API and another in registration are handled at the same time without two engineers switching branches. What comes back is small enough to review in minutes and carries its own failing-then-passing test.

## Frequently asked questions

### Which .NET SDK does the sandbox use?

The one `global.json` pins, 10.0.103 with `rollForward` set to `latestFeature`, which is what `actions/setup-dotnet` resolves in the `test.yml` workflow. The agent reads that file first and does not build with a different major version.

### Does it run every test project in the solution?

No. It runs the test project that owns the changed code, `test/Api.Test` or `test/Identity.Test` for a controller, and builds the solution. The workflow's full `dotnet test ./test` plus the `bitwarden_license/test` tree runs in your CI on the pull request, and the description says what was run in the sandbox.

### Does the test need SQL Server or the integration factories?

Not for a controller or service exception. The unit tests stub repositories with NSubstitute through `SutProvider`, so they run in process. `test/Api.IntegrationTest` uses `ApiApplicationFactory` with an in-process Identity; the agent runs it only when the fix spans the request pipeline and the prompt allows it.

### What if the exception is already a BadRequestException?

Then it is a 400 the API intends, and it should not have reached Sentry as an error. The agent says so in its final message, points at the filter in `src/Libraries/ExceptionHandling` that maps it, and opens no pull request unless the mapping is what is broken.
