# Upgrade Cargo Dependencies in a Rust CLI

Yes. An agent upgrades a Rust CLI's Cargo dependencies in groups, runs the tests, clippy and the MSRV check after each, and opens one PR with the skips explained.

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

## What the agent does

The agent reads `Cargo.toml` and `Cargo.lock` to learn what is pinned and what floats. In casey/just the package is `just` 1.59.0, edition 2024, `rust-version` 1.89.0, in a workspace with `crates/action-versions` and `crates/generate-book`; `Cargo.lock` resolves 155 packages. Nearly every dependency is a caret range on an old floor, `clap` `4.0.0`, `serde` `1.0.130`, `regex` `1.10.4`, `snafu` `0.9.0`, `similar` `3.0.0`, `tempfile` `3.0.0`, which the lock has already carried forward to `clap` 4.6.7, `serde` 1.0.229, `regex` 1.13.1, `similar` 3.2.0 and `tempfile` 3.27.0. One crate is pinned exactly: `clap_complete = "=4.6.11"` with the `unstable-dynamic` feature, and the changelog records why (`Pin clap_complete to last compatible version`, #2800). `pulldown-cmark` appears twice in the lock, 0.13.4 for the main crate's `src/tangle.rs` and 0.9.6 for `generate-book`, which asks for `0.9.1` and still declares edition 2018. There is no Dependabot or Renovate configuration under `.github/`, so the lock moves only when someone runs `cargo update`.

Then it reads what a green build proves. `.github/workflows/ci.yaml` sets `RUSTFLAGS: --deny warnings` and runs four jobs: `lint` with `cargo clippy --all --all-targets`, `cargo fmt --all -- --check`, `./bin/forbid` (a ripgrep for `dbg!` and `todo` outside the changelog) and `shellcheck www/install.sh`; `msrv` with `cargo check` on 1.89.0; `pages`, which builds the book with `cargo run --package generate-book` and mdbook 0.4.52; and `test` with `cargo test --all` on Ubuntu, macOS and Windows plus a run of `www/install.sh`. `Cargo.toml` denies clippy `all` and `pedantic` and the `unreachable_pub` lint, so a new deprecation warning from an upgraded crate is a failed build. The tests are one harness: `autotests = false` and a single `[[test]]` named `integration` at `tests/lib.rs`, which pulls in 118 modules through a `Test` builder in `tests/test.rs` that writes a justfile to a tempdir, runs the built binary and compares stdout and stderr with `pretty_assertions`. `tests/completions.rs` checks `--completions` for bash, elvish, fish, nushell, powershell and zsh, and `tests/man.rs` checks `--man` prints a `.TH just 1` header, so the `clap` family is tested through its output.

The groups follow those facts. `cargo update` first, which moves transitive crates and any direct crate still inside its range, then one group at a time for crates with a newer minor or major than the range allows: the `clap` family (`clap`, `clap_complete` with its pin raised only if the completion and man tests pass, `clap_mangen`) together because `src/main.rs` uses `CompleteEnv` and `src/lib.rs` uses `ArgValueCompleter` and `PathCompleter` from the dynamic completion engine; `pulldown-cmark` for the main crate alone, since `generate-book` pairs its 0.9 with `pulldown-cmark-to-cmark` 10; then `snafu`, `strum`, `similar`, `semver`, `nix`, `which` and the rest each on their own. After each group the agent runs `cargo test --all`, `cargo clippy --all --all-targets` with `--deny warnings`, `cargo fmt --all -- --check` and `cargo check` on the 1.89.0 toolchain, and for the `clap` group also `cargo run -- --completions zsh` and `--man` by eye in the transcript, since those outputs are what the tests compare.

A group that passes is a commit with the manifest change and the lock; a group that fails, such as a `clap_complete` release that changes the dynamic completion output the tests match, is reverted with the first error kept. The agent bumps the floor in `Cargo.toml` only when the new version is needed to compile, and otherwise leaves the range and moves the lock, which is what the justfile's `ci` entry expects with `cargo update --locked --package just`. The pull request lists one line per bump with old and new versions, the crates left out with their error, and the four check results.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Upgrade this Rust CLI's Cargo dependencies. Read Cargo.toml, Cargo.lock and .github/workflows/ci.yaml first; in a crate shaped like casey/just, use RUSTFLAGS=--deny warnings and the rust-version in Cargo.toml for an MSRV check. Run cargo update first and keep it as its own commit if the tests pass. Then upgrade in groups the crates a newer minor or major exists for: the clap family (clap, clap_complete, clap_mangen) together, raising an exact pin only if the completion and man tests still pass; pulldown-cmark for the main crate alone; then each remaining crate on its own. After each group run cargo test --all, cargo clippy --all --all-targets, cargo fmt --all -- --check and cargo check on the MSRV toolchain. Keep a group only if everything passes; otherwise revert it and record the first error. Raise a version floor in Cargo.toml only when the new version is required to compile. Open one pull request with one commit per group, one line per bump with old and new versions, each skipped crate with its error, and the check results.",
  "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 on a new branch with one commit per group that passed: `Cargo.lock` in every commit, `Cargo.toml` only where a floor or the `clap_complete` pin had to move, and no source changes unless a group needed a one-line port, which its commit message names. The crates in `crates/` keep their own manifests unless their group was taken.

The description is one line per bump with old and new versions, the groups reverted with the compiler, clippy or test error that excluded them, and the results of `cargo test --all`, clippy under `--deny warnings`, the fmt check and `cargo check` on 1.89.0. The transcript keeps the full output of every group, including the reverted ones, so a maintainer can see exactly which completion test a `clap_complete` release broke.

Before merging, a reviewer looks at the `clap` commit first, because `tests/completions.rs` and `tests/man.rs` compare generated text, then checks that `www/install.sh` and the book build were not affected, and that no floor was raised beyond what compiling required, since a higher floor is a change for everyone who builds from source.

## Run it on every Monday morning

Create an automation in the Project with this prompt and a `schedule` trigger (`cron` `0 9 * * 1` in your `timezone`). The first run catches the lock up; each week after that opens one pull request with that week's bumps, or none when `cargo update` changes nothing and no crate has a new minor. Because each group is its own commit, dropping a bump before merge is a revert of one commit.

To run it from your own scheduler instead, send this body to `POST /o/{org}/sessions` once a week with an `Idempotency-Key` built from the repository name and the ISO week, and set a webhook URL on the API key: Rush POSTs `run.completed` with `session_url` when the turn ends, and your handler can assign the pull request to whoever owns dependencies that week. A crate that failed stays at its old version and is tried again the following week.

## Why run it on Rush?

A CLI with 118 integration test modules that compare exact output, a `--deny warnings` policy and a pinned completion crate cannot be upgraded by a bot that bumps everything at once: the red pull request says nothing about which crate broke which test. On Rush the agent takes one group at a time in its own sandbox, keeps the groups that pass and the error for the ones that do not, and the pull request that reaches you is already green on the same commands CI runs.

The record shows every test run, every clippy run and the MSRV check per group, which is the evidence a maintainer needs when the crate that moved is the one generating shell completions. The same request runs against every Rust CLI you maintain at once.

## Frequently asked questions

### Why run cargo update before touching Cargo.toml?

Because most floors here are old caret ranges, so the lock can move a long way with no manifest change. `cargo update` is the cheap first commit; the groups after it are only for crates whose newer release is outside the range, and the justfile's `ci` entry runs `cargo update --locked --package just` to confirm the lock is consistent.

### Will it raise the clap_complete pin?

It tries, in the `clap` group, and keeps the change only if `tests/completions.rs` and `tests/man.rs` still pass, since the pin exists because a later release changed the dynamic completion behavior. If they fail, the pin stays and the description quotes the failing test.

### Does it touch generate-book and action-versions?

Only in the `cargo update` commit, where their transitive crates move, unless you name them in the prompt. `generate-book` pairs `pulldown-cmark` 0.9 with `pulldown-cmark-to-cmark` 10, and moving one without the other does not compile, so the agent treats that pair as its own group when asked.

### What about the MSRV?

The `msrv` job runs `cargo check` on 1.89.0, and a dependency release that needs a newer compiler fails there. The agent runs the same check after each group and reverts a group that raises the effective MSRV, listing the crate and the version that would require a `rust-version` bump.

### Does it run the Windows and macOS matrix?

No. The sandbox is Linux, and the description says so. A crate that is only used on another platform, such as `ctrlc` under `cfg(windows)`, is upgraded on the strength of `cargo check` and listed as not exercised by the tests.
