All jobs

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

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

The use case this job follows, with its automation and trigger: Write the missing tests.

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.