All jobs

Fix Rocket Errors from Sentry Alerts

Yes. An agent reads the Sentry event, finds the Rocket handler or guard it names, reproduces the error with a test, fixes it and opens a PR with the results.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads the alert payload after the prompt, then fetches the issue and its latest event through the Sentry connection. A Rocket server reports two kinds of event: a panic, with the message and the frames of the thread that unwound, or an error-level log line captured by a logging integration, with the log target as the module path. In dani-garcia/vaultwarden the second kind is the common one, because every err! in src/error.rs calls error!("{msg}") before it returns, so the event message is the exact text the handler produced and the target names the module, such as vaultwarden::api::core::ciphers.

From there it reads the error type. make_error! in src/error.rs defines Error { message, kind, code, event, silent } with From impls for Diesel connection, pool and query errors, Rocket, serde_json, lettre, reqwest, webauthn-rs and yubico_ng errors, so a database failure and a validation failure arrive at the client through the same path: api_error serializes an ApiErrorResponse with the message, and err! defaults the status to 400. Handlers are async fn returning JsonResult or EmptyResult from src/api/mod.rs, with request guards from src/auth.rs (Headers, OrgHeaders, AdminHeaders, ManagerHeaders, OwnerHeaders, OrgMemberHeaders) and a DbConn guard that src/db/mod.rs takes from the managed pool. Queries run through conn.run(move |conn| ...) or the db_run! macro, which dispatches on the active backend. src/api/core/organizations.rs, ciphers.rs and accounts.rs hold most handlers at 3113, 2260 and 1966 lines, so the module path in the event is what keeps the search short.

The test goes where the repository already puts tests: a #[cfg(test)] mod tests in the module, as src/api/core/accounts.rs, src/api/admin.rs and src/auth.rs have. For a pure cause, a wrong date format, a key case conversion, a flag parsed wrongly, the agent calls the function with the value from the event and asserts the right output. For a handler cause it builds a Rocket with that module's routes mounted and drives it with rocket::local::blocking::Client, giving the request the headers the guard needs, and asserts the status and body; a route that needs a DbConn gets a DbPool from DbPool::from_config() with DATABASE_URL pointing at a temporary SQLite file, which the embedded migrations/sqlite bring up on first connect. The test must fail with the same message as the event before the fix counts.

The fix stays in the function the frames name and does not change the error contract: a None that was unwrapped becomes an err! with a message in the style of its neighbors, a Diesel query that assumed one row uses the pattern the model already has in src/db/models/, and a guard that refused a valid header is corrected in src/auth.rs. Then the agent runs the CI commands from .github/workflows/build.yml with RUSTFLAGS=-Dwarnings: cargo test --profile ci --features sqlite, cargo clippy --profile ci --features sqlite,mysql,postgresql,enable_mimalloc,s3, and cargo fmt --all -- --check on the 1.99.0 toolchain from rust-toolchain.toml. A change to a query also has to compile for mysql and postgresql, since the schema types differ per backend, and clippy with all three features is how CI checks that.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "A Sentry alert fired for this Rocket server; its payload follows this prompt. Use the Sentry connection to read the issue, its latest event, the message, the log target or the panic frames, and any request tags. In a server shaped like dani-garcia/vaultwarden, the error-level log line an err! macro writes in src/error.rs is usually the event message and the log target is the module; open that module under src/api/ or src/auth.rs and read the handler, its request guards and the queries it runs through conn.run or db_run!. Add a test in a #[cfg(test)] mod tests of that module: call the function directly when the cause is pure, or mount the module's routes on a test Rocket and drive them with rocket::local::blocking::Client, with DATA_FOLDER set to a temporary directory and, when a DbConn is needed, DATABASE_URL pointing at a temporary SQLite file. Confirm the test fails with the same message as the event, then fix the cause in that function without changing the error response shape or catching the panic. Run cargo test --profile ci --features sqlite, cargo clippy --profile ci --features sqlite,mysql,postgresql,enable_mimalloc,s3 and cargo fmt --all -- --check with RUSTFLAGS=-Dwarnings. Open a pull request that links the Sentry issue, quotes the message and frames and the failing test output, and explains the cause. If it does not reproduce, stop and report 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 that changes one function under src/api/, src/auth.rs or src/util.rs and adds a test in that module's mod tests. The description links the Sentry issue, quotes the message and the frames or log target, shows the test failing before the change and passing after, and gives the results of cargo test --profile ci --features sqlite, the three-backend clippy run and the fmt check. Cargo.toml, Cargo.lock and migrations/ do not change.

Before merging, a reviewer checks that the fix keeps the ApiErrorResponse shape the Bitwarden clients parse, that a query change still compiles for MySQL and Postgres and not just SQLite, and that a guard change in src/auth.rs does not widen who a header admits. When the event came from a release older than main and the handler already changed, the description says so and the test pins the current behavior.

Run it on every Sentry issue alert

Add a webhook action to the alert rule in the Sentry project and point it at a handler on your server, because Sentry signs its own requests and cannot add the Authorization: Bearer header a Rush webhook trigger expects. The handler puts the alert JSON after the prompt and sends this request with the Sentry issue id as Idempotency-Key, so a redelivered alert returns the run that already started.

With a webhook URL on the API key, Rush POSTs run.completed with session_url and the result within about 30 seconds of the turn ending, and the handler can leave the pull request link as a comment on the issue. Sentry groups repeats into one issue, so one alert is one run; a burst of distinct issues is several runs, up to the key's max_concurrent.

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

Why run it on Rush?

A Vaultwarden-style server has 58 SQLite migrations, three database backends, a 1.4-thousand-line auth module and a hard -Dwarnings policy, so reproducing one error on a laptop means a long first build and a carefully set DATA_FOLDER and DATABASE_URL. On Rush the agent does that in its own sandbox with the pinned 1.99.0 toolchain, runs the one module's tests first and the full feature matrix after, and hands back a diff that is already green on the commands CI runs.

The record is the proof: the Sentry event the agent read, the test failing with the event's message and passing after the change, and the exact clippy output across sqlite, mysql and postgresql. A reviewer judges the fix rather than rebuilding the request, and ten alerts from ten servers are ten sandboxes at once.

Frequently asked questions

The server logs with fern and log, not tracing. What does Sentry get?

Whatever your logging integration forwards: with sentry-log the error-level line each err! writes becomes an event whose message is the text and whose target is the module path. The agent reads both from the event; without a logging integration it falls back to the panic frames the Sentry panic handler reports.

Does the test need MySQL or Postgres?

No. A handler test runs against a temporary SQLite file through --features sqlite, and the embedded migrations/sqlite create the schema on first connect. The three-backend clippy run from CI checks that a query change compiles for the other two; the description says the tests themselves ran on SQLite.

What if the frames point at the admin page or the web vault?

src/api/admin.rs has its own mod tests and its own catchers, so the agent writes the test there and drives the route with the admin token the handler expects. A failure inside the bundled web vault assets is a client bug, and the result says so instead of changing the server.

Will it add a migration?

Not from an alert. A fix that needs a schema change touches all four migrations/ backends and src/db/schema.rs, which is a separate pull request; the agent explains the cause, pins it with a test, and stops.

What if it cannot reproduce the error?

It stops and reports what it tried: the request or input it built from the event, the test it wrote, and what the code returned. Common gaps are a header the event did not keep or a row shape only the production database has, and the result names them so you can decide.