# Add Test Coverage Reporting to Rust CI

Yes. An agent measures your Rust crate's line coverage with cargo-llvm-cov, adds a CI job with a threshold at that number and an lcov artifact, and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/add-coverage-to-ci/rust/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how the crate is tested before it adds a job. In a Rust project like casey/just, `Cargo.toml` names the `just` crate at 1.59.0 on edition 2024 with `rust-version` 1.89.0, turns `doctest` off, marks the `[[bin]]` with `test = false`, and declares one `[[test]]` target, `integration`, at `tests/lib.rs`, which pulls in 118 test modules. `.github/workflows/ci.yaml` has four jobs: `lint` runs `cargo clippy --all --all-targets`, `cargo fmt --all -- --check`, `./bin/forbid` and `shellcheck`; `msrv` checks with toolchain 1.89.0; `pages` builds the book; and `test` runs `cargo test --all` on Ubuntu, macOS and Windows. The workflow sets `RUSTFLAGS: --deny warnings` and `CARGO_PROFILE_DEV_DEBUG: 0` globally. Nothing mentions coverage: not the workflow, not the `justfile`, not `Cargo.toml`.

One detail in `tests/lib.rs` decides the tooling. The integration tests do not call library functions; they run the real binary. `tests/lib.rs` reads the `CARGO_BIN_EXE_just` variable Cargo sets at compile time into a `JUST` constant, and `Command::new(JUST)` spawns `just` as a child process for every test. A coverage tool that only instruments the test executable would report the crate as almost untested. `cargo llvm-cov` instruments the binary too, because Cargo builds `CARGO_BIN_EXE_just` with the same instrumented flags, and its profile file pattern includes the process id, so every child writes its own `.profraw` and the report merges them. The agent confirms this in the sandbox by running `cargo llvm-cov --all --summary-only` and checking that `src/` shows real line coverage rather than a row of zeros.

It also reads how actions are pinned. `.github/workflows/actions.lock` is machine-generated by `gh actions-lock` and says not to edit it by hand, and every `uses:` line in `ci.yaml` carries a full version. So the new `coverage` job reuses `actions/checkout@v6.1.0` and `Swatinem/rust-cache@v2.9.2` from the same file, installs `cargo-llvm-cov` with `cargo install --locked` so no new action is needed, adds the `llvm-tools-preview` component, runs `cargo llvm-cov --all --lcov --output-path lcov.info`, prints the summary into `$GITHUB_STEP_SUMMARY`, and fails the job with `--fail-under-lines` set to the number the sandbox measured, rounded down. Uploading `lcov.info` uses `actions/upload-artifact` at the version `release.yaml` already pins, and the agent regenerates `actions.lock` with `gh actions-lock` as the file's header instructs; if that tool is not available in the sandbox it says so in the pull request and leaves the lockfile for the reviewer to regenerate.

The job runs on `ubuntu-latest` only, because the matrix in `test` already proves the suite on three platforms and coverage needs one. It adds a `coverage` entry to the `justfile` that runs the same command locally, since the project's own workflow is driven by `just`. Then it pushes, waits for the workflow on the pull request, and reads the job log: the summary table, the threshold check and the artifact upload. A red job means it reads why, fixes the workflow and pushes again.

## Start it with one API call

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

```json
{
  "agent": "codex",
  "input": "Add test coverage reporting to this Rust crate's CI. Read Cargo.toml, .github/workflows/ci.yaml, .github/workflows/actions.lock and tests/lib.rs first. Measure line coverage here with cargo llvm-cov across the whole crate, confirming that coverage of the binary the integration tests spawn through CARGO_BIN_EXE_just is captured. Add a coverage job to ci.yaml on ubuntu-latest that reuses the already pinned checkout and rust-cache actions, installs cargo-llvm-cov with cargo install --locked, produces lcov.info, writes the summary to the step summary, fails under the line percentage you measured rounded down, and uploads lcov.info as an artifact with the upload-artifact version release.yaml already pins. Regenerate actions.lock with gh actions-lock if it is available, otherwise say so. Add a coverage entry to the justfile. Write no new tests. Open a pull request, wait for the workflow run and fix the job until it is green.",
  "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 that changes `.github/workflows/ci.yaml` to add the `coverage` job, changes `justfile` to add a `coverage` entry, and changes `actions.lock` when `gh actions-lock` ran. No source file and no test changes. The description shows the coverage summary the agent measured in the sandbox, names the threshold it set from that number, and explains why the job can see the spawned binary's coverage.

A reviewer opens the workflow run on the pull request and reads the `coverage` job: the step summary with per-file lines, the threshold step passing, and `lcov.info` listed under the run's artifacts. They check that the job reuses the pinned actions rather than introducing floating tags, that the threshold is not higher than what the run measured, and that `RUSTFLAGS: --deny warnings` did not have to be relaxed to get there.

The session's transcript keeps the local `cargo llvm-cov` run with its full table, so the number in the workflow traces back to a command and its output rather than to an estimate.

## Run it on every new Rust crate repository

When a new crate repository is created, a handler on your server receives GitHub's repository event and sends `POST /o/{org}/sessions` with this prompt and the repository as the `resources` URL. Each run measures that crate in its own sandbox and sets its own threshold, so a young crate is not held to a mature crate's number.

Set a `webhook_url` on the API key and Rush POSTs `run.completed` when the turn finishes, with the `session_url` to read the pull request from.

## Why run it on Rush?

Adding coverage to a crate whose tests spawn their own binary is a trap on a laptop: the first tool you try reports near zero, and an hour goes to working out whether the tests or the tool are wrong. The agent runs the real command in its own sandbox, reads the table, and only then writes the job, so the threshold in the pull request is a measurement rather than a wish.

The job arrives with a green run on the pull request and the local measurement in the transcript beside it. A reviewer compares the two numbers and merges. Several crates can get the same treatment in parallel, each with a threshold at its own level.

## Frequently asked questions

### The tests spawn the binary. Does coverage see it?

Yes, with `cargo llvm-cov`. Cargo builds the binary behind `CARGO_BIN_EXE_just` with the same instrumentation flags as the test, and the profile file pattern includes the process id, so each spawned `just` writes its own profile and the report merges them. The agent checks the `src/` rows in the summary before trusting it.

### Where does the threshold number come from?

From the sandbox run of `cargo llvm-cov` on your crate, rounded down to a whole percent. It is a floor that stops coverage from falling, not a target. Raise it in the workflow when you want to.

### Will it add a new pinned action and break actions.lock?

It avoids new actions where it can: `cargo-llvm-cov` is installed with `cargo install --locked`, and checkout and rust-cache are the versions `ci.yaml` already pins. The one addition, `upload-artifact`, reuses the version `release.yaml` pins, and the lockfile is regenerated with `gh actions-lock` as its header says; if that tool is missing in the sandbox the pull request says so rather than hand-editing the file.

### Will it write tests to raise coverage?

Not in this run; the prompt says so, and the job's purpose is to report and hold the line. A follow-up message to the same session can ask for tests on the least-covered files in the table, and it continues in the same sandbox.

### Does it need a third-party coverage service?

No. The number lives in the step summary and the `lcov.info` artifact on each run. If you use a service, add its upload step after the artifact step; the lcov file is the format they take.
