# Upgrade Rust Dependencies Every Week

Yes. Each week an agent raises your Cargo versions, runs cargo test against them, reverts any crate that breaks the build, and opens one pull request.

[Run this on Rush](https://cloud.getrush.ai/run/upgrade-dependencies-weekly/rust/), in a Project that has your repository.

## What the agent does

A Rust service like rust-lang/crates.io is a Cargo workspace: the root Cargo.toml lists members = ["crates/*"], which covers 28 internal crates from crates_io_database to crates_io_worker, and Cargo.lock resolves 841 packages. The toolchain is fixed by rust-toolchain.toml at channel 1.99.1, so the agent builds with exactly that compiler. The first thing it notices is that every direct dependency is pinned with an equals sign: axum = "=0.8.9", diesel = "=2.3.14", diesel-async = "=0.9.2", tokio = "=1.53.3", reqwest = "=0.13.5". With pins like those, cargo update only moves transitive crates, so an upgrade here means editing the version strings in Cargo.toml.

The agent lists which pinned crates have newer releases and separates semver-compatible bumps (tokio 1.53 to a later 1.x) from breaking ones (axum 0.8 to 0.9, where a 0.x minor is a breaking change under Cargo's rules). It edits the compatible ones in Cargo.toml and the matching entries in any crates/*/Cargo.toml that pin the same crate, then lets cargo update -p rewrite the lock entry. Breaking bumps go into the pull request as a list of what is available, unless the prompt asks for them.

Tests here need Postgres. The CI workflow creates a cargo_registry_test database with C collation, exports TEST_DATABASE_URL=postgres://postgres:postgres@localhost/cargo_registry_test, then runs cargo build --tests --workspace and cargo insta test --require-full-match --unreferenced=reject --workspace. The agent sets up the same database and runs cargo test --workspace, with cargo insta test when snapshot files change, so a dependency that alters JSON output or error text shows up as a snapshot diff instead of passing quietly.

When the workspace fails to compile or a test goes red, the agent narrows it to one crate by returning half the bumps at a time to their old pins. A crate that breaks goes back to its previous version and its first compiler error or failing test is recorded. Before opening the pull request it also runs cargo clippy --all-targets --all-features --workspace, cargo deny check and cargo machete, the same checks the repository's ci.yml runs, so the upgrade does not trade a passing test suite for a failing lint or a license policy violation in deny.toml.

## Start it with one API call

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

```json
{
  "agent": "codex",
  "input": "Upgrade this Cargo workspace's dependencies. Use the toolchain in rust-toolchain.toml. Most direct dependencies are pinned with =; for each one with a newer semver-compatible release, raise the pin in every Cargo.toml that names it and run cargo update -p for that crate. List breaking releases (a new 0.x minor or a new major) without applying them. Create the Postgres test database the CI workflow creates and set TEST_DATABASE_URL, then run cargo test --workspace, and cargo insta test --require-full-match if snapshots change. For every crate whose bump fails to compile or fails a test, restore its old pin and record the first error. Run cargo clippy --all-targets --all-features --workspace and cargo deny check on the result. Open one pull request with a table of each crate's old and new version, each skipped crate with its error, and the available breaking upgrades.",
  "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

The pull request edits the root Cargo.toml, any member crate manifests that pin the same crates, and Cargo.lock. The description opens with a table of crates and their old and new pins, then the crates that were tried and put back, each with the rustc error code or the failing test path, then the breaking releases that are available but not applied (with the version that would be needed). Transitive updates that came with the lockfile are summarised by count rather than listed one by one.

Before merging, a reviewer looks at the crates that sit on the request path (axum, tower, tokio, diesel-async) and reads their release notes, checks that no insta snapshot was accepted without a reason, and confirms ci.yml on the pull request is green, including the cargo deny and cargo machete steps. The session transcript has each cargo command and its output, so a revert can be checked against the actual compiler message.

## Run it on every weekly maintenance slot

Your scheduler, whether a cron line on a server or a scheduled GitHub Actions job, sends the same POST /o/{org}/sessions body once a week for each Rust repository, with the repository's URL in resources. An Idempotency-Key that combines the repository name with the ISO week keeps a retried job from starting a duplicate run.

With a webhook URL on the key, your endpoint gets a signed run.completed event when the agent finishes, carrying session_url. Fetch the session for the pull request and assign it to whoever owns dependencies that week. Crates that failed stay pinned at their old version, so next week's run tries them again with whatever new patch release has shipped.

## Why run it on Rush?

A clean build of a workspace this size compiles hundreds of crates, and the bisection step can rebuild it several times. On a laptop that occupies every core and the target directory; in a Rush sandbox the agent has its own machine, its own Postgres for TEST_DATABASE_URL, and the output of every cargo invocation kept in the session record for anyone checking why a crate was left behind.

Renovate is already set up on crates.io, and its .github/renovate.json5 needs a hand-written groupName rule just to move the diesel packages together; every other crate arrives as its own pull request. That works when each one is green alone, but an exact-pin workspace often needs crates raised together (axum with tower, for instance) that nobody has written a rule for. One agent run proposes them as a set, tested together, and several repositories can run their weekly upgrade in parallel up to the key's concurrency limit.

## Frequently asked questions

### Why doesn't cargo update alone upgrade my dependencies?

cargo update only picks newer versions that the requirements in Cargo.toml already allow. A requirement like =0.8.9 allows exactly one version, so the agent has to edit the requirement itself. With caret requirements such as "1.2", cargo update does most of the work and the agent's job is the testing and reverting.

### Will it bump a crate to a breaking release?

Not by default. Under Cargo's semver rules a change in the first non-zero version number is breaking, so axum 0.8 to 0.9 counts. The agent lists those as available and leaves them for a separate request that asks for one migration at a time.

### What happens with insta snapshots?

If a dependency changes serialized output, cargo insta test reports a pending snapshot. The agent does not accept it silently: it either reverts the crate or, when the change is expected, accepts it and names the snapshot file and the reason in the pull request.

### Does it change rust-toolchain.toml?

No. The toolchain is a separate decision from crate versions, so the agent builds with the channel the file names. If a new crate release needs a newer rustc than that, the crate is skipped and its required Rust version is listed.
