# Turn Sentry Errors Into Fix Pull Requests for a Go Service

Yes. A Sentry alert starts a run; the agent follows the goroutine frames into your Go packages, adds a failing test, fixes it and opens a pull request.

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

## What the agent does

A Sentry event from a Go service carries the error value or the panic message and a stack of frames with full package paths, so the agent can place each one without guessing. Through the Sentry connection it reads the issue, the latest event, its tags and breadcrumbs, and the frames, then opens its clone of the repository and reads the functions those frames name, top of the stack first. The alert payload itself arrives after the prompt as text, and the agent treats it as data.

In a Go service like miniflux/v2 the frames land in the feed refresh path. `go.mod` declares `go 1.26.0`, so the sandbox uses that toolchain. `RefreshFeed` in `internal/reader/handler/handler.go` loads the feed with `store.FeedByID`, builds a request with the feed's credentials, user agent, cookie, proxy and TLS settings through `fetcher.NewRequestBuilder`, adds `ETag` and `Last-Modified` unless the feed ignores the HTTP cache, and wraps the answer in `fetcher.NewResponseHandler`. It then checks `IsRateLimited` and `ParseRetryDelay`, `LocalizedError`, `AnotherFeedURLExists`, `IsModified`, reads the body with the configured maximum size, parses it with `parser.ParseFeed` and takes the largest of the feed TTL, `Cache-Control` max-age and `Expires` as the next refresh delay. `LocalizedError` in `internal/reader/fetcher/response_handler.go` turns TLS, network, timeout and `io.EOF` client errors and the 401, 403, 404, 410, 429 and 5xx status codes into localized errors. Three callers run it: `refreshFeedHandler` in `internal/api/feed_handlers.go`, which answers `JSONServerError` or `NoContent`; `refreshFeed` in `internal/ui/feed_refresh.go`, which logs a warning with `slog.Warn` and redirects; and the worker in `internal/worker/worker.go`. A nil feed, an unexpected header or a body the parser rejects each surfaces in one of these.

The agent writes the regression test in the package's existing `_test.go` file where one exists. `response_handler_test.go` already has `TestIsModified`, `TestRetryDelay`, `TestExpiresInMinutes`, `TestCacheControlMaxAgeInMinutes` and `TestIsCloudflareChallenge`, table-driven with a hand-built `http.Response`, so a header or status case goes there as another row. The `handler` package has no test file, so a case in `RefreshFeed` gets a new `handler_test.go` with an `httptest.Server` serving the exact response the Sentry breadcrumbs show. The agent runs `go test ./internal/reader/... -run <Name>` and keeps the failure, which reproduces the panic or the wrong error.

Then it fixes the code, reruns the test until it passes, and runs `make test`, which is `go test -cover -race -count=1 ./...`, and `make lint`, which is `go vet`, a `gofmt -l` check and `golangci-lint run`. Tests under `internal/api` include `api_integration_test.go`, which the `make integration-test` target runs against a Postgres database named `miniflux_test`; the agent runs that target only when the fix touches that package and the sandbox has Postgres up. The pull request says which targets ran.

## Start it with one API call

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

```json
{
  "agent": "codex",
  "input": "A Sentry alert fired for this Go service; its payload follows this prompt. Use the Sentry connection to read the issue, its latest event, breadcrumbs and the stack frames with their package paths. Read the functions the frames name, starting with RefreshFeed in internal/reader/handler/handler.go and the response handler in internal/reader/fetcher/response_handler.go. Add a regression test, as a new row in the package's existing table-driven _test.go file where there is one, otherwise a new _test.go file using httptest, that reproduces the error. Fix the code until the test passes, then run `make test` and `make lint`. Open a pull request that links the Sentry issue, quotes the frames and the failing test output, and explains the cause. If the cause is outside this repository, open no pull request and say what you found.\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

You get a pull request with a focused diff in one package and a new test case beside it, either a row in an existing table or a new `_test.go` file. The description links the Sentry issue, quotes the error and the frames with their package paths, shows the test failing before and passing after, and lists the `make test` and `make lint` output summaries. Your own CI runs on the branch as on any pull request.

The session keeps the whole record: the Sentry calls, each `go test` run including the race detector's output, `go vet` and `golangci-lint`, and every file the agent opened. `GET /o/{org}/sessions/{id}/messages` returns it as events, and the `run.completed` webhook carries the agent's final message as `data.result` with a `session_url` for the rest.

Before merging, a reviewer checks that the new test fails on the old code for the reason Sentry reported, that a fix in the response handler still returns the localized error the UI and API expect rather than swallowing it, and that a retry or scheduling change still honours `Retry-After`. If the fix should sit in a different layer, a follow-up message to the session moves it and the agent updates the same branch.

## Run it on every Sentry issue alert

Give the Sentry project an alert rule 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 Sentry issue id as `Idempotency-Key`, so Sentry's retries return the run already started. One issue, one sandbox, one pull request.

Set a webhook URL on the API key and Rush POSTs `run.completed` with `session_url` when the turn finishes; the handler reads the session, takes the pull request from it and comments the 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 Go fix is quick to write and slow to trust. You want the exact response that broke the fetcher inside a test, the race detector green and the linter quiet before anyone merges, and doing that by hand means a toolchain, a Postgres and an afternoon. In a Rush sandbox the agent has the Go version `go.mod` names, runs the repository's own `Makefile` targets and keeps their output with the session.

Every issue becomes its own run in its own clone, so a parser failure and an unrelated handler panic are fixed at the same time on separate branches, in parallel up to the key's `max_concurrent` limit. The person who reviews reads a pull request that quotes its evidence and a transcript that shows where each line came from.

## Frequently asked questions

### Which Go version does the sandbox use?

The one `go.mod` declares, `go 1.26.0` here. The agent reads that line and uses a matching toolchain so the tests run on the same version as the `tests.yml` workflow, which runs `make test` on Ubuntu and `go test ./...` on Windows and macOS.

### Does it run the integration tests that need Postgres?

Only when the fix touches `internal/api` or storage and you allow it in the prompt. The `integration-test` target creates a `miniflux_test` database with `psql`, so the sandbox needs Postgres running. Otherwise `make test` covers the unit tests with coverage and `-race`.

### What if the Sentry event is a panic with no in-app frame?

The agent uses the panic message, the goroutine stack and the breadcrumbs to find the entry point, usually one of the three `RefreshFeed` callers, and works back from there. When the frames all sit in a dependency, it says so and proposes the smallest change on your side, or opens no pull request.

### Will the test hit a real feed URL?

No. It serves the response with `httptest.Server` or builds an `http.Response` by hand, as the existing tests in `response_handler_test.go` do, so the suite stays offline and deterministic.
