All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Fix a bug from an alert.

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.