# Fix Sentry Errors in a Rails App

Send the Sentry issue to an agent: it reads the Rails stack trace, reproduces the error in a Minitest case, fixes the controller or job and opens a pull request.

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

## What the agent does

The agent reads the issue after the prompt and fetches its latest event through the Sentry connection: the exception class, the message, the transaction, the breadcrumbs and the in-app frames. In a Rails app the breadcrumbs are rich when the SDK is configured for it, and in codetriage/CodeTriage it is: `config/initializers/sentry.rb` sets `breadcrumbs_logger` to `[:active_support_logger, :http_logger]`, excludes `Sidekiq::Shutdown`, and samples traces at 0.1, so the event carries the SQL, the cache hits and the outbound HTTP calls that led to the exception. `app/jobs/sentry_job.rb` sends events through a background job, which means a job error and a request error look alike until the transaction is read.

CodeTriage runs Rails 8.1.3.1 on Ruby 4.0.5 with Postgres, Puma, Sidekiq and Devise, with `sentry-ruby` and `sentry-rails` 7.0 in `Gemfile.lock`. A request transaction names a controller and action: `repos#show`, `repos#list`, `repos#create`, `issue_assignments#click_issue_redirect`, `repo_subscriptions#update`, `users#update` or the Devise `omniauth_callbacks`. A job transaction names one of the classes in `app/jobs/`: `PopulateIssuesJob`, `UpdateRepoInfoJob`, `SendDailyTriageEmailJob`, `SendSingleTriageEmailJob`, `PopulateDocsJob` or `BackgroundInactiveEmailJob`, most of which call GitHub through `app/models/github_fetcher.rb` and its classes.

The frames point at a controller, a model or a job, and the agent reads the chain around it. A `NoMethodError` on nil in `repos#show` usually traces to a repo whose GitHub data has not been populated; an error in `IssueAssigner` or `issue_assignments#click_issue_redirect` to an assignment whose issue was closed; an error in `UpdateRepoInfoJob` to a GitHub response the fetcher did not expect. The breadcrumbs show the HTTP calls, so the agent can see the response status that preceded the exception.

Tests are Minitest under `test/`: `test/functional/repos_controller_test.rb`, `issue_assignments_controller_test.rb`, `repo_subscriptions_controller_test.rb` and `users_controller_test.rb` for controllers, `test/jobs/populate_issues_job_test.rb` and `update_repo_info_job_test.rb` for jobs, `test/unit/` for models, with fixtures loaded for all and GitHub stubbed through `webmock` and VCR cassettes in `test/vcr_cassettes`, as `test/test_helper.rb` sets up. The agent adds one test that drives the same request or job with the state the breadcrumbs describe, confirms it fails with the Sentry exception, fixes the code, runs `bin/rake test` for that file and then in full, as `main.yml` does after `rake db:create db:schema:load`, runs `bundle exec standardrb` on the changed files, and opens a pull request that links the Sentry issue and quotes the frames and the test output.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Sentry issue for this Rails repository is attached below. Read its latest event through the Sentry connection: the exception, the transaction (controller#action or job class), the breadcrumbs with SQL and HTTP calls, the release and the in-app frames. Open the controller, model or job the frames name under app/controllers, app/models or app/jobs and read the chain around the failing line. Add one Minitest case to the matching file under test/functional, test/jobs or test/unit that reproduces the request or job with the state the breadcrumbs describe, stubbing GitHub with webmock or a VCR cassette as the neighbouring tests do, and confirm it fails with the same exception. Fix the cause rather than rescuing it, run bin/rake test for that file and then the whole suite after rake db:create db:schema:load, run bundle exec standardrb on the files you changed, and open a pull request that links the Sentry issue, quotes the frames and the failing test output and explains the cause. Stop at needs_review if you cannot reproduce it.\n\nSentry issue:\n{issue}",
  "connections": [
    "sentry"
  ],
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

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 that changes one controller action, model method or job and adds one Minitest case, for example a test in `test/functional/repos_controller_test.rb` that requests a repo with no fetched issues and now expects a page rather than a 500. The description links the Sentry issue, names the transaction and the frame, states the cause, shows the test failing then passing, and gives the `bin/rake test` summary and the `standardrb` result.

The reviewer checks that the test uses fixtures and stubs the way its neighbours do rather than hitting GitHub, that the fix handles the state rather than rescuing the exception, and that a job change does not alter retry behavior in a way Sidekiq would amplify. A controller change should keep Devise's authentication filters as they were.

When the run ends `needs_review`, no pull request exists; the result explains what could not be reproduced, for example a GitHub response shape the cassettes do not contain, so a maintainer can supply it or close the issue.

## Run it on every Sentry issue alert

Create an alert rule in the Sentry project with a webhook action pointed at a handler on your server. The handler sends this request with the issue JSON after the prompt and the Sentry issue id as `Idempotency-Key`, so a repeated delivery returns the run already started. A Rush `webhook` trigger wants `Authorization: Bearer` with its secret, which Sentry's own webhook cannot send; the handler adds it if you route through an automation in the Project instead.

Set a webhook URL on the API key and Rush POSTs `run.completed` or `run.failed` with the `session_url` when the turn finishes; the handler can comment the pull request link on the Sentry issue. An issue from a background job may fire on every Sidekiq retry, so the handler should dedupe on issue id.

## Why run it on Rush?

Rails apps with background jobs produce Sentry issues that need a specific database state and a specific GitHub response to reproduce, and the maintainer of a volunteer project does not have an afternoon for each one. The agent builds the database from `db:schema:load`, loads the fixtures, stubs GitHub the way the suite already does, and hands back a pull request. Several issues can be several sessions at once.

The pull request is reviewable on its evidence: the event read, the Minitest case failing with the same exception, the full `bin/rake test` run afterwards and the `standardrb` check. A fix that only rescues the error would pass review nowhere, and the prompt forbids it.

## Frequently asked questions

### Does the agent need GitHub access to reproduce job errors?

No. `test/test_helper.rb` loads `webmock/minitest` and the suite keeps VCR cassettes under `test/vcr_cassettes`, so the agent stubs the GitHub response the breadcrumbs show. If the shape it needs is in no cassette, it writes a webmock stub in the test with that shape.

### Which Ruby and database does the sandbox use?

Ruby 4.0.5 from `.ruby-version` with Bundler's cached gems, and a local Postgres created with `rake db:create db:schema:load` using `config/database.ci.yml` copied into place, the same steps as the `main.yml` workflow. Nothing touches a production database.

### Will it run the whole suite?

Yes, after the single file passes: `bin/rake test` in full, as CI does, so a fix in a model does not break a controller test elsewhere. The pull request description shows both runs.

### What if the error is in Devise or OmniAuth rather than app code?

The frames say so, and the fix stays in the app's side of it, for example `app/controllers/users/omniauth_callbacks_controller.rb` handling a missing email from GitHub. The agent does not patch gems; if the cause is in a gem, it says so and stops at `needs_review`.

### Does it add the Sentry context to the fix?

Not unless the cause was missing context. The fix is the code change plus the test; the pull request description, not the code, carries the link to the issue and the quoted frames.
