GitHub Action wrapper for planner gates and
scope-json.
- Runs
cargo rail planin planner-owned GitHub transport mode. - Publishes planner gates and
scope-jsonfor job gating. - Keeps CI behavior aligned with local
plan+runworkflows.
Minimum planner contract: cargo-rail >= 0.11.0.
name: CI
on: [push, pull_request]
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: loadingalias/cargo-rail-action@v4
id: rail
with:
version: "0.11.0"
- name: Run targeted tests
if: steps.rail.outputs.test == 'true'
env:
SCOPE_JSON: ${{ steps.rail.outputs.scope-json }}
run: |
MODE=$(echo "$SCOPE_JSON" | jq -r '.mode')
if [ "$MODE" = "workspace" ]; then
cargo nextest run --workspace
elif [ "$MODE" = "crates" ]; then
mapfile -t CRATES < <(echo "$SCOPE_JSON" | jq -r '.crates[]')
ARGS=()
for crate in "${CRATES[@]}"; do
ARGS+=(-p "$crate")
done
cargo nextest run "${ARGS[@]}"
else
echo "planner enabled test surface, but no package-scoped work was selected"
fi
- name: Run docs pipeline
if: steps.rail.outputs.docs == 'true'
run: cargo rail run --since "${{ steps.rail.outputs.base-ref }}" --surface docsUse the stable major tag @v4, or pin a commit SHA for maximum reproducibility. Pin version for deterministic
cargo-rail installs.
The action handles shallow checkouts intentionally: raw commit SHAs are fetched directly when possible, and broader history fetch only happens when a branch-based comparison still needs merge-base history.
| Input | Default | Description |
|---|---|---|
version |
0.11.0 |
cargo-rail version to install (use latest only if you intentionally want floating upgrades) |
checksum |
required |
required, if-available, or off |
since |
auto | Git ref for planner comparison |
args |
"" |
Extra planner args except -f/--format, --json, and -o/--output |
working-directory |
. |
Workspace directory |
token |
${{ github.token }} |
Token for release download API |
mode |
minimal |
minimal (recommended) or debug |
Optional args examples:
# Explain plan decisions in job logs
- uses: loadingalias/cargo-rail-action@v4
with:
args: '--explain'
# Use different comparison ref
- uses: loadingalias/cargo-rail-action@v4
with:
since: 'origin/develop'Gates are boolean outputs (true/false) used in job conditions. They answer: "should this CI surface run?"
How gates work:
- The planner (
cargo rail plan) analyzes changed files against your.config/rail.tomlrules - It classifies changes into surfaces:
build,test,bench,docs,infra - The action publishes these as GitHub Action outputs:
build=true,test=false, etc. - You use these in job
if:conditions to skip unnecessary work
Why gates exist:
- Deterministic: same plan locally and in CI (run
cargo rail plan --merge-baseto preview) - Configurable: you define infrastructure files, doc-only paths, etc. in
rail.toml - Composable: combine gates (
if: steps.rail.outputs.build == 'true' || steps.rail.outputs.infra == 'true')
Modes control what outputs are published:
minimal(default): gates,scope-json,base-ref, and per-custom-surface booleansdebug: minimal outputs plusplan-json
| Output | Type | Use |
|---|---|---|
build |
true/false |
Gate build jobs — set when code changes affect compilation |
test |
true/false |
Gate test jobs — set when changes affect test outcomes |
bench |
true/false |
Gate benchmark jobs — set when changes affect performance tests |
docs |
true/false |
Gate docs jobs — set for doc comments, README, markdown changes |
infra |
true/false |
Gate infra jobs — set for CI config, scripts, toolchain changes |
scope-json |
JSON | Compact execution-scope payload emitted by cargo-rail |
base-ref |
string | Git ref for downstream run calls (--since "${{ steps.rail.outputs.base-ref }}") |
custom_<name> |
true/false |
Custom surface gates, with punctuation normalized to _ |
Includes all minimal outputs plus the full planner contract.
| Output | Type | Description |
|---|---|---|
plan-json |
JSON | Full deterministic planner contract for debugging and deep inspection |
Note: Most workflows should use minimal mode. scope-json is the execution handoff; use it for package selection in scripts and jobs. plan-json is forensic/debug output only.
Example:
- name: Show targeted crates
run: echo '${{ steps.rail.outputs.scope-json }}' | jq -r '.crates[]'Custom surfaces are exported as custom_<name> outputs. Example:
- name: Run benchmark suite
if: steps.rail.outputs.custom_benchmarks == 'true'
run: cargo bench --workspaceThe action tries installation methods in this order:
-
Cached binary (fastest) — uses GitHub Actions cache (
actions/cache)- Why: Subsequent runs on same runner are instant (no download)
- Cache reuse requires exact version match; when
version: latest, the action resolves latest first, then compares
-
Release binary (fast) — downloads from GitHub Releases
- Why: Pre-built binaries for all supported platforms, verified against
SHA256SUMS - Supports all platforms except macOS Intel (see below)
- Why: Pre-built binaries for all supported platforms, verified against
-
cargo-binstall(fallback) — installs via binstall if available- Why: Faster than
cargo install(downloads binary instead of compiling) - Used when platform is unsupported for release binaries
- Why: Faster than
-
cargo install(slowest, last resort) — compiles from source- Why: Guaranteed to work on any platform with Rust toolchain
- Adds ~2-5 minutes to workflow run time
Default is required: download SHA256SUMS and verify binary integrity.
Why: Supply chain security. Detects corrupted downloads and tampering.
Options:
required(default): fail if checksum missing or mismatchedif-available: verify ifSHA256SUMSexists, skip if not (for pre-release testing)off: skip verification (not recommended for production)
macOS Intel (x86_64-apple-darwin) binaries are not published in releases.
Why: GitHub Actions deprecated macos-latest Intel runners in 2024. All macOS runners are now ARM (macos-14, macos-15). Publishing Intel binaries wastes release bandwidth for a platform no longer used in CI.
Workaround: The action falls back to cargo-binstall or cargo install on Intel Macs (primarily for local testing, not CI).
Action + planner flow validated on production repos with real merge history:
| Repository | Crates | Fork |
|---|---|---|
| tokio-rs/tokio | 10 | Config + Guide |
| helix-editor/helix | 14 | Config + Guide |
| meilisearch/meilisearch | 23 | Config + Guide |
| helixdb/helix-db | 6 | Config + Guide |
Validation forks: cargo-rail-testing — full configs, integration guides, and reproducible artifacts.
Real-world benefits:
- Docs-only PRs skip build/test entirely (run only docs checks)
- Infrastructure changes (CI config, scripts) trigger full rebuild (no false-negatives)
- Isolated crate changes run targeted tests (not entire workspace)
- Plan matches local behavior (
cargo rail plan --merge-basepreviews CI gates)
Measured impact (last 20 commits per repo):
| Repository | Could Skip Build | Could Skip Tests | Targeted (Not Full Run) |
|---|---|---|---|
| tokio | 10% | 0% | 95% |
| meilisearch | 35% | 35% | 60% |
| helix | 30% | 30% | 40% |
| helix-db | 10% | 10% | 75% |
| Aggregate (80 commits) | 21% | 19% | 68% |
Each fork includes:
.config/rail.toml— production-ready config with custom surfacesdocs/cargo-rail-integration-guide.md— step-by-step CI integration with workflow examplesdocs/CHANGE_DETECTION_METRICS.md— measured impact analysis on recent commits
Reproducible command matrix and examples live in cargo-rail:
- https://github.com/loadingalias/cargo-rail/blob/main/examples/validation-protocol.md
- https://github.com/loadingalias/cargo-rail/tree/main/examples/change_detection
- Action issues: GitHub Issues
- Core tool: loadingalias/cargo-rail
See CONTRIBUTING.md.
See SECURITY.md.
See CODE_OF_CONDUCT.md.
Licensed under MIT.