Complete reference for all rail.toml configuration options. Kept in sync with source defaults and CLI behavior.
cargo-rail uses rail.toml for workspace-level configuration. This file controls:
- Dependency unification (
cargo rail unify) - Release automation (
cargo rail release) - Change planning + execution (
cargo rail plan,cargo rail run) - Crate splitting (
cargo rail split)
Configuration files are searched in order:
rail.toml(workspace root).rail.toml(workspace root, hidden).cargo/rail.toml(cargo directory).config/rail.toml(config directory)
The first file found is used. All paths are relative to the workspace root.
Generate a default configuration file:
cargo rail init # Creates .config/rail.toml
cargo rail init -o rail.toml # Creates rail.toml
cargo rail init --check # Preview without writing
cargo rail init --force # Overwrite existing configcargo-rail works with sensible defaults. An empty file is valid; add options only when you need them:
# Minimal config (optional): set targets if you want multi-target validation.
# (`cargo rail init` can auto-detect targets from *.toml and .github/workflows.)
targets = ["x86_64-unknown-linux-gnu"]# Multi-target workspace with unify enabled
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
]
[unify]
msrv = true # Compute MSRV from dependencies
prune_dead_features = true # Remove unused features
detect_unused = true # Find unused dependencies
compiler_diag_cache = true # Reuse rustc diagnostics across runs
remove_unused = true # Auto-remove them
[release]
tag_format = "{crate}-{prefix}{version}"
create_github_release = false
[change-detection]
infrastructure = [".github/**", "justfile"]Configuration options at the workspace root level.
| Option | Type | Default | Description |
|---|---|---|---|
targets |
string[] |
[] |
Target triples for multi-platform validation. Used by unify and other commands to run cargo metadata --filter-platform for each target. Auto-detected by cargo rail init. |
unify |
table |
{} |
Dependency unification settings (see below) |
release |
table |
{} |
Release management settings (see below) |
change-detection |
table |
{} |
Change detection settings (see below) |
run |
table |
{} |
Run profile settings for cargo rail run (see below) |
crates |
table |
{} |
Per-crate configuration (see below) |
Example:
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]Controls workspace dependency unification behavior. All options are optional with sensible defaults.
| Option | Type | Default | Description |
|---|---|---|---|
msrv |
bool |
true |
Compute and write MSRV to [workspace.package].rust-version (written as major.minor.patch). The MSRV is determined by msrv_source. |
enforce_msrv_inheritance |
bool |
false |
Ensure every workspace member inherits MSRV by setting [package].rust-version = { workspace = true } in each member's Cargo.toml. This makes [workspace.package].rust-version actually apply across the workspace. |
msrv_source |
enum |
"max" |
How to compute the final MSRV: • "deps" - Use maximum from dependencies only (original behavior)• "workspace" - Preserve existing rust-version, warn if deps need higher• "max" - Take max(workspace, deps) - your explicit setting wins if higher |
detect_unused |
bool |
true |
Detect unused dependencies using two signals: (1) declared deps absent from the resolved cargo graph, and (2) rustc unused_crate_dependencies diagnostics for deps that resolve but are never referenced in source. Optional deps and deps behind unconfigured target constraints are conservatively skipped. |
compiler_diag_cache |
bool |
true |
Cache target-aware rustc unused_crate_dependencies diagnostics in target/cargo-rail/cache/compiler-diags-v1.json and reuse them across runs. Disable to force fresh compiler checks each run. |
remove_unused |
bool |
true |
Automatically remove unused dependencies during unification. Requires detect_unused = true. |
prune_dead_features |
bool |
true |
Remove features that are never enabled in the resolved dependency graph across all targets. Only prunes empty no-ops (feature = []). Features with actual dependencies are preserved. |
preserve_features |
string[] |
[] |
Features to preserve from dead feature pruning. Supports glob patterns (e.g., "unstable-*", "bench*"). Use this to keep features intended for future use or external consumers. |
detect_undeclared_features |
bool |
true |
Detect crates that rely on Cargo's feature unification to "borrow" features from other workspace members. These crates will fail when built standalone after unification. Reports as warnings (or auto-fixes if fix_undeclared_features is enabled). |
fix_undeclared_features |
bool |
true |
Auto-fix undeclared feature dependencies by adding missing features to each crate's Cargo.toml. Produces a cleaner graph where standalone builds work correctly. Requires detect_undeclared_features = true. |
skip_undeclared_patterns |
string[] |
["default", "std", "alloc", "*_backend", "*_impl"] |
Patterns for features to skip in undeclared feature detection. Supports glob patterns. Default patterns filter out features that are typically not actionable (standard library features, internal implementation details). |
max_backups |
usize |
3 |
Maximum number of backup archives to keep. Older backups are automatically cleaned up after successful operations. Set to 0 to disable backup creation entirely. |
Example:
[unify]
msrv = true
msrv_source = "max" # "deps" | "workspace" | "max"
enforce_msrv_inheritance = false
detect_unused = true
compiler_diag_cache = true
remove_unused = true
prune_dead_features = true
preserve_features = ["future-api", "unstable-*"] # Keep these from pruning
detect_undeclared_features = true # Catch borrowed features
fix_undeclared_features = true # Auto-fix them (default)
skip_undeclared_patterns = ["default", "std", "alloc", "*_backend", "*_impl"] # Features to skip
max_backups = 5| Option | Type | Default | Description |
|---|---|---|---|
strict_version_compat |
bool |
true |
When true, version mismatches between member manifests and existing workspace.dependencies are blocking errors. When false, they are warnings only. |
exact_pin_handling |
enum |
"warn" |
How to handle exact version pins like =0.8.0:• "skip" - Exclude exact-pinned deps from unification• "preserve" - Keep the exact pin operator in workspace.dependencies• "warn" - Convert to caret (^) but emit a warning |
major_version_conflict |
enum |
"warn" |
How to handle major version conflicts (e.g., serde = "1.0" and serde = "2.0"):• "warn" - Skip unification, emit warning (both versions stay in graph)• "bump" - Force unify to highest resolved version (may break code) |
Example:
[unify]
strict_version_compat = false
exact_pin_handling = "preserve"
major_version_conflict = "bump"Notes:
- In my experience,
major_version_conflict = "bump"works in most cases; some may require code fixes - Use
"warn"for safety,"bump"for the leanest build graph - If
[workspace.package].rust-versionis missing but root[package].rust-versionis present,unifyuses it as the baseline and writes it to[workspace.package].rust-version(consider enablingenforce_msrv_inheritanceto avoid drift)
| Option | Type | Default | Description |
|---|---|---|---|
include_paths |
bool |
true |
Include path dependencies in unification. If false, path dependencies are excluded. |
include_renamed |
bool |
false |
Include renamed dependencies (package = "..."). When enabled, features are aggregated across all variants using union. Opt-in due to complexity. |
exclude |
string[] |
[] |
Dependencies to skip from unification (safety hatch). Useful for platform-specific or problematic dependencies. For workspace-member dependency cohorts, excluding one member excludes the full cohort atomically to prevent local-vs-registry splits. |
include |
string[] |
[] |
Force-include specific dependencies in unification, even if they're single-use. Workspace-member cohorts are auto-included by cargo-rail to avoid threshold-based cohort splits. |
Example:
[unify]
include_paths = true
include_renamed = false
exclude = ["openssl", "windows-sys"] # Platform-specific
include = ["my-special-dep"] # Force includeWorkspace-member cohort rule: cargo-rail unifies connected workspace-member dependency sets atomically. A partial outcome (some members local, siblings from crates.io) is blocked automatically.
Advanced feature for replacing workspace-hack crates. Only enable if you currently use cargo-hakari.
| Option | Type | Default | Description |
|---|---|---|---|
pin_transitives |
bool |
false |
Pin transitive-only dependencies with fragmented features. This is cargo-rail's workspace-hack replacement. When enabled, transitive deps with multiple feature sets are pinned in workspace.dependencies. |
transitive_host |
string |
"root" |
Where to put pinned transitive dev-dependencies: • "root" - Use workspace root Cargo.toml• "crates/foo" - Use specific member crate (relative path from workspace root) |
Example:
[unify]
pin_transitives = true
transitive_host = "root"Complete Example:
[unify]
# Core options (defaults shown)
msrv = true
msrv_source = "max" # "deps" | "workspace" | "max"
enforce_msrv_inheritance = false
detect_unused = true
compiler_diag_cache = true
remove_unused = true
prune_dead_features = true
preserve_features = [] # Glob patterns to preserve from pruning
max_backups = 3
# Version handling
strict_version_compat = true
exact_pin_handling = "warn"
major_version_conflict = "warn"
# Dependency selection
include_paths = true
include_renamed = false
exclude = []
include = []
# Transitive pinning (workspace-hack replacement)
pin_transitives = false
transitive_host = "root"Release automation settings for versioning, tagging, and publishing.
| Option | Type | Default | Description |
|---|---|---|---|
tag_prefix |
string |
"v" |
Git tag prefix. Used via {prefix} placeholder in tag_format. |
tag_format |
string |
"{crate}-{prefix}{version}" |
Tag template. Available variables: • {crate} - Crate name• {version} - Version number• {prefix} - Value of tag_prefix |
require_clean |
bool |
true |
Require clean working directory before release operations. |
publish_delay |
u64 |
5 |
Delay between crate publishes in seconds. Allows crates.io to propagate dependencies. |
create_github_release |
bool |
false |
Automatically create GitHub releases via gh CLI after tagging. Requires gh to be installed and authenticated. |
sign_tags |
bool |
false |
Sign git tags with GPG or SSH. Requires git signing to be configured. |
Example:
[release]
tag_prefix = "v"
tag_format = "{crate}-{prefix}{version}" # Produces: my-crate-v1.0.0
require_clean = true
publish_delay = 10
create_github_release = true
sign_tags = true| Option | Type | Default | Description |
|---|---|---|---|
changelog_path |
string |
"CHANGELOG.md" |
Default changelog filename for all crates. |
changelog_relative_to |
enum |
"crate" |
What changelog paths are relative to: • "crate" - Relative to each crate's directory• "workspace" - Relative to workspace root |
skip_changelog_for |
string[] |
[] |
Crate names that should not generate changelog entries. |
require_changelog_entries |
bool |
false |
If true, error when there are no changelog entries for a crate being released. |
require_release_notes |
bool |
true |
If true, preflight fails release apply when the target version has no release notes (## [<version>]) and changelog generation produces no entries. Set to false to allow note-less releases. |
Example:
[release]
changelog_path = "CHANGELOG.md"
changelog_relative_to = "crate"
skip_changelog_for = ["internal-utils"]
require_changelog_entries = trueComplete Example:
[release]
# Core
tag_prefix = "v"
tag_format = "{crate}-{prefix}{version}"
require_clean = true
publish_delay = 5
create_github_release = false
sign_tags = false
# Changelog
changelog_path = "CHANGELOG.md"
changelog_relative_to = "crate"
skip_changelog_for = []
require_changelog_entries = false
require_release_notes = trueNotes:
- In monorepos, use
{crate}intag_formatto avoid tag collisions - For single-crate workspaces, use
tag_format = "v{version}" changelog_relative_to = "workspace"is useful for unified changelogs
Settings for planner path classification.
| Option | Type | Default | Description |
|---|---|---|---|
infrastructure |
string[] |
see below | Path patterns treated as infra changes. |
unknown_file_policy |
enum |
"strict" |
Unknown-file policy: • "docs" - keep unknown files docs-only• "owned_build_test" - crate-owned unknown files enable build + test• "workspace_infra" - non-crate unknown files enable infra• "strict" - crate-owned unknown files enable build + test; everything else enables infra |
confidence_profile |
enum |
"balanced" |
Planner confidence profile: • "strict" - expands crate-owned changes to conservative build + test with transitive seeding• "balanced" - default behavior• "fast" - disables conservative transitive surface seeding for speed |
bot_pr_confidence_profile |
enum? |
unset |
Optional profile override applied only for bot-authored GitHub pull requests (for example set to "strict"). |
custom |
table<string, string[]> |
{} |
Custom path patterns. Emits custom:<name> surfaces in cargo rail plan output. Important: Custom surfaces are plan OUTPUTS for CI gating—they cannot be used in [run.profile.X].surfaces. Category names must use ASCII letters/digits with _ or - (for example verify_models, bench-extended). |
Default Infrastructure Patterns:
infrastructure = [
".github/**",
"scripts/**",
"justfile",
"Justfile",
"Makefile",
"makefile",
"GNUmakefile",
"*.sh",
"Taskfile.yml",
"Taskfile.yaml",
".pre-commit-config.yaml",
"deny.toml",
"cliff.toml",
"release.toml",
"release-plz.toml",
]Example:
[change-detection]
infrastructure = [
".github/**",
"justfile",
"Cargo.lock",
"rust-toolchain.toml"
]
confidence_profile = "balanced"
bot_pr_confidence_profile = "strict" # optional; only active for bot PRs
[change-detection.custom]
verify = ["verify/**/*.rs"]
benchmarks = ["benches/**", "perf/**"]
docs = ["docs/**", "*.md"]Custom surfaces serve a different purpose than built-in surfaces:
| Aspect | Built-in Surfaces | Custom Surfaces |
|---|---|---|
| Values | build, test, bench, docs, infra |
custom:<name> (user-defined) |
Use in [run.profile.X].surfaces |
✅ Yes | ❌ No |
| Appears in plan output | ✅ Yes | ✅ Yes |
| Use in CI job gating | ✅ Yes | ✅ Yes (via scope_json.surfaces or action custom_<name> outputs) |
Why this distinction? Built-in surfaces map to cargo commands (cargo build, cargo test, etc.). Custom surfaces are arbitrary categories for CI decision-making—they don't map to cargo commands, so they can't be "executed" by cargo rail run.
Custom surfaces are additive. If a path also matches a built-in classification such as
infra, docs, or bench, cargo rail plan enables both the built-in surface and the
matching custom:<name> surface(s). Custom routing is an overlay, not a replacement for
core planner semantics.
CI gating pattern for custom surfaces:
- uses: loadingalias/cargo-rail-action@v4
id: rail
- name: Run benchmark suite
if: steps.rail.outputs.custom_benchmarks == 'true' || steps.rail.outputs.infra == 'true'
run: cargo benchUse cargo rail plan -f github as the CI source of truth:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build plan outputs
id: plan
run: cargo rail plan --merge-base -f github >> "$GITHUB_OUTPUT"
- name: Test selected crates
if: steps.plan.outputs.test == 'true'
run: cargo rail run --merge-base --profile ci
- name: Docs pipeline
if: steps.plan.outputs.docs == 'true'
run: cargo rail run --merge-base --surface docsFor a full end-to-end operating guide (local + CI + trust checklist), see: docs/change-detection.md.
plan -f github outputs:
| Output | Description |
|---|---|
build |
"true" when build surface is enabled |
test |
"true" when test surface is enabled |
bench |
"true" when bench surface is enabled |
docs |
"true" when docs surface is enabled |
infra |
"true" when infra surface is enabled |
base_ref |
Resolved baseline ref used for change detection |
scope_json |
Compact execution handoff emitted by the planner |
Use cargo rail plan -f github-debug when you also need plan_json for debugging or incident review.
cargo rail run writes a deterministic decision receipt under target/cargo-rail/receipts/.
Upload it in CI so incident/debug review can answer "why this ran" without log spelunking:
- name: Run targeted surfaces
run: cargo rail run --merge-base --profile ci
- name: Upload rail receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: cargo-rail-receipts
path: target/cargo-rail/receipts/*.json
if-no-files-found: ignore| Legacy pattern | Planner-first replacement |
|---|---|
docs-only == true |
docs == true and test == false |
rebuild-all == true |
infra == true |
| surface dispatch | cargo rail run --surface test |
custom-categories checks |
Parse scope_json.surfaces["custom:<name>"] or use action custom_<name> outputs |
Planner outputs support four formats:
| Format | Use Case | Example |
|---|---|---|
text |
Human-readable summary | plan\nsurfaces: build, test |
json |
Full machine contract | {"files":[...],"surfaces":{...}} |
github |
GitHub key/value outputs | test=true |
github-debug |
GitHub outputs plus plan_json |
plan_json={...} |
Execution profile configuration for cargo rail run.
| Option | Type | Default | Description |
|---|---|---|---|
default_profile |
string? |
unset |
Default profile when no --surface/--profile is passed. |
profile |
table |
{} |
User-defined profile map: [run.profile.<name>]. |
workflow |
table<string,string> |
{} |
Optional workflow-name to profile-name mapping for CI wrappers. |
Built-in profiles:
local->["test"]ci->["build", "test"]nightly->["build", "test", "docs"]
Precedence:
--surfaceoverrides all profile selection.--profileoverridesrun.default_profile.run.default_profileoverrides built-in fallback (local).
User-defined profile schema:
| Field | Type | Required | Description |
|---|---|---|---|
surfaces |
string[] |
yes | Built-in run surfaces to execute: build, test, bench, docs. Note: infra and custom surfaces (defined in [change-detection.custom]) are planner outputs for CI job gating, not profile inputs. |
run_args |
string[] |
no | Args prepended before CLI RUN_ARGS. |
since |
string? |
no | Default --since baseline when CLI does not pass --since/--merge-base. |
merge_base |
bool? |
no | Default merge-base mode when CLI does not pass --since/--merge-base. |
Token substitutions (stable expansion order):
{workspace_root}{base_ref}{cargo_args}(only valid inrun_args)
Allowed tokens by field:
run.profile.<name>.run_args:{workspace_root},{base_ref},{cargo_args}run.profile.<name>.since:{workspace_root},{base_ref}
Example:
[run]
default_profile = "ci"
[run.workflow]
commit = "ci"
nightly = "nightly"
bench-weekly = "bench_weekly"
[run.profile.bench]
surfaces = ["bench"]
run_args = ["--", "--bench", "core"]
since = "origin/main"
[run.profile.bench_weekly]
surfaces = ["bench"]
run_args = ["--", "--bench", "critical", "{cargo_args}"]
since = "{base_ref}"Per-crate configuration. Replace NAME with the actual crate name from Cargo.toml.
Crate splitting and syncing configuration. Enables extracting crates to separate repositories.
| Option | Type | Required | Description |
|---|---|---|---|
remote |
string |
yes | Remote repository URL (git) or local path (for testing). |
branch |
string |
yes | Git branch to sync with. |
mode |
enum |
yes | Split mode: • "single" - One crate per repository• "combined" - Multiple crates in one repository |
workspace_mode |
enum |
no | For mode = "combined" only:• "standalone" - Multiple standalone crates• "workspace" - Workspace structure with root Cargo.toml |
paths |
CratePath[] |
yes | Crate paths to include. Format: [{ crate = "path/to/crate" }]• mode = "single" requires exactly 1 path• mode = "combined" requires 2+ paths |
include |
string[] |
no | Additional files/directories to include in the split (e.g., ["LICENSE", "README.md"]) |
exclude |
string[] |
no | Files/directories to exclude from the split |
Choosing a Mode:
| Scenario | Mode | Result |
|---|---|---|
| Publish one crate independently | single |
Files at repo root, standalone Cargo.toml |
| Group related utility crates | combined + standalone |
Preserves directory structure, independent crates |
| Extract as sub-workspace | combined + workspace |
Root Cargo.toml with [workspace] |
Single Crate Example:
[crates.my-lib.split]
remote = "git@github.com:org/my-lib.git"
branch = "main"
mode = "single"
paths = [
{ crate = "crates/my-lib" }
]
include = ["LICENSE", "README.md"]
exclude = ["*.tmp"]Combined Workspace Example:
[crates.utils.split]
remote = "git@github.com:org/utils-mono.git"
branch = "main"
mode = "combined"
workspace_mode = "workspace"
paths = [
{ crate = "crates/string-utils" },
{ crate = "crates/io-utils" },
{ crate = "crates/math-utils" }
]
include = ["LICENSE"]Local Testing:
[crates.test-crate.split]
remote = "/tmp/test-split-repo" # Local path for testing
branch = "main"
mode = "single"
paths = [{ crate = "crates/test-crate" }]Per-crate release configuration. Overrides workspace-level release defaults.
| Option | Type | Default | Description |
|---|---|---|---|
publish |
bool |
true |
Enable/disable publishing for this crate. Overrides Cargo.toml publish field. |
Example:
[crates.internal-utils.release]
publish = false # Never publish to crates.ioPer-crate changelog configuration.
| Option | Type | Default | Description |
|---|---|---|---|
path |
PathBuf |
Custom changelog path for this crate. Overrides workspace-level changelog_path. Interpreted according to release.changelog_relative_to. |
|
skip |
bool |
false |
Exclude this crate from changelog generation entirely. |
Example:
[crates.my-lib.changelog]
path = "CHANGES.md" # Use CHANGES.md instead of CHANGELOG.md
skip = false
[crates.private-crate.changelog]
skip = true # No changelog for internal cratesAfter initial split, use cargo rail sync for bidirectional synchronization:
cargo rail sync my-lib # Auto-detect direction
cargo rail sync my-lib --to-remote # Monorepo → split repo
cargo rail sync my-lib --from-remote # Split repo → monorepo (PR branch)Key behaviors:
- Idempotent: Uses git-notes to track synced commits; re-running only processes new commits
- PR branch protection:
--from-remotecreatescargo-rail-sync-<crate>branch, never commits to main - Conflict resolution:
--strategycontrols merge behavior (manual,ours,theirs,union)
# Complete rail.toml showing all available options
# Top-level: Multi-target support
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]
# Dependency unification
[unify]
# Core
msrv = true
msrv_source = "max" # "deps" | "workspace" | "max"
enforce_msrv_inheritance = false
detect_unused = true
compiler_diag_cache = true
remove_unused = true
prune_dead_features = true
preserve_features = [] # Glob patterns: ["unstable-*", "future-api"]
detect_undeclared_features = true # Catch borrowed features
fix_undeclared_features = true # Auto-fix them
skip_undeclared_patterns = ["default", "std", "alloc", "*_backend", "*_impl"]
max_backups = 3
# Version handling
strict_version_compat = true
exact_pin_handling = "warn"
major_version_conflict = "warn"
# Dependency selection
include_paths = true
include_renamed = false
exclude = ["openssl", "windows-sys"]
include = []
# Transitive pinning (workspace-hack replacement)
pin_transitives = false
transitive_host = "root"
# Release automation
[release]
tag_prefix = "v"
tag_format = "{crate}-{prefix}{version}"
require_clean = true
publish_delay = 5
create_github_release = true
sign_tags = true
# Changelog
changelog_path = "CHANGELOG.md"
changelog_relative_to = "crate"
skip_changelog_for = []
require_changelog_entries = true
require_release_notes = true
# Change detection
[change-detection]
infrastructure = [
".github/**",
"scripts/**",
"justfile",
"Makefile",
"*.sh",
"Cargo.lock",
]
[change-detection.custom]
verify = ["verify/**/*.rs"]
benchmarks = ["benches/**"]
docs = ["docs/**", "*.md"]
# Per-crate configuration
[crates.my-lib]
[crates.my-lib.split]
remote = "git@github.com:org/my-lib.git"
branch = "main"
mode = "single"
paths = [
{ crate = "crates/my-lib" }
]
include = ["LICENSE", "README.md"]
exclude = []
[crates.my-lib.release]
publish = true
[crates.my-lib.changelog]
path = "CHANGELOG.md"
skip = false
[crates.internal-utils]
[crates.internal-utils.release]
publish = false
[crates.internal-utils.changelog]
skip = trueLet cargo-rail handle everything with sensible defaults:
targets = ["x86_64-unknown-linux-gnu"]Replace cargo-hakari with cargo-rail's transitive pinning:
[unify]
pin_transitives = true
transitive_host = "root"Force unification to highest versions, accept breaking changes:
[unify]
major_version_conflict = "bump"
strict_version_compat = false
exact_pin_handling = "preserve"Disable automatic cleanup and MSRV management:
[unify]
prune_dead_features = false
remove_unused = false
msrv = false
detect_unused = true # Still detect, just don't removeHandle platform-specific dependencies:
targets = [
"x86_64-unknown-linux-gnu",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
]
[unify]
exclude = [
"openssl", # Linux-specific
"windows-sys", # Windows-specific
"core-foundation" # macOS-specific
]Complete configuration for automated releases and testing:
targets = ["x86_64-unknown-linux-gnu"]
[unify]
pin_transitives = true
msrv = true
detect_unused = true
compiler_diag_cache = true
remove_unused = true
prune_dead_features = true
[release]
tag_prefix = "v"
tag_format = "{crate}-{prefix}{version}"
require_clean = true
require_changelog_entries = true
require_release_notes = true
create_github_release = true
sign_tags = true
[change-detection]
infrastructure = [".github/**", "justfile", "Cargo.lock"]
confidence_profile = "strict"
bot_pr_confidence_profile = "strict"
[change-detection.custom]
benchmarks = ["benches/**"]
[crates.my-lib.split]
remote = "git@github.com:org/my-lib.git"
branch = "main"
mode = "single"
paths = [{ crate = "crates/my-lib" }]
include = ["LICENSE", "README.md"]Bidirectional sync between monorepo and split repositories:
[crates.frontend.split]
remote = "git@github.com:org/frontend.git"
branch = "main"
mode = "combined"
workspace_mode = "workspace"
paths = [
{ crate = "crates/ui" },
{ crate = "crates/components" }
]
include = ["assets/**", "LICENSE"]
exclude = ["*.tmp", ".DS_Store"]
[crates.backend.split]
remote = "git@github.com:org/backend.git"
branch = "main"
mode = "single"
paths = [{ crate = "crates/server" }]cargo-rail provides comprehensive configuration validation via cargo rail config validate:
cargo rail config validate # Validate rail.toml
cargo rail config validate --strict # Treat warnings as errors
cargo rail config validate --no-strict # Force warnings-only mode
cargo rail config validate -f json # JSON output for CI integration- Syntax - TOML parse errors with line/column information
- Unknown keys - Typos like
mrsv_sourceinstead ofmsrv_source - Semantic validation - Split config requirements, target triple formats
- Deprecation warnings - Future-proofing for config migrations
By default, validation runs in strict mode when CI is detected (via CI, GITHUB_ACTIONS, GITLAB_CI, or CIRCLECI environment variables):
- In CI: Unknown keys and other warnings become errors (exit code 2)
- Locally: Unknown keys are warnings only
Override with --strict or --no-strict flags.
# .github/workflows/ci.yml
- name: Validate config
run: cargo rail config validate
# Auto-strict in CI - fails on unknown keys| Error | Cause |
|---|---|
| TOML parse error at line X | Syntax error (missing quotes, invalid structure) |
| Unknown top-level key 'foo' | Typo in section name |
| Unknown key 'bar' in [unify] | Typo in field name or deprecated option |
| Missing required field: remote | Split config without remote URL |
| 'foo' doesn't look like a valid target | Target triple missing architecture separator |
Replace cargo hakari generate with cargo-rail:
# Before (cargo-hakari)
# [workspace.dependencies]
# hakari = { version = "0.1.0", path = "hakari" }
# After (cargo-rail)
[unify]
pin_transitives = true
transitive_host = "root" # or a path to a workspace member crate (relative to workspace root)Then run:
cargo rail unifycargo-rail provides similar functionality with tighter integration:
# release-plz.toml → rail.toml
[release]
tag_format = "{crate}-{prefix}{version}"
require_changelog_entries = true
require_release_notes = true
create_github_release = trueNo cargo-rail-specific environment variables are required. For reproducibility, configuration is file-based.
Note: cargo rail config validate defaults to strict mode in CI (detected via CI, GITHUB_ACTIONS, GITLAB_CI, or CIRCLECI).
- Commands Reference - All cargo-rail commands
- Migration Guide - Migrating from cargo-hakari
- Troubleshooting - Diagnose planner and executor decisions
- README - Project overview and quick start