CI Troubleshooting
CI Troubleshooting
Section titled “CI Troubleshooting”Common CI failure patterns and fixes for rust-template. Use this runbook when a workflow fails on a pull request or push to main.
General Debugging
Section titled “General Debugging”Reading Workflow Logs
Section titled “Reading Workflow Logs”- Go to Actions: https://github.com/zircote/rust-template/actions
- Click the failed workflow run
- Click the failed job (red X)
- Expand the failed step to see the full log
- Click “Search logs” (magnifying glass) to search for error messages
CLI Approach
Section titled “CLI Approach”# List recent failed runsgh run list --status failure --limit 10
# View a specific rungh run view <run-id>
# View logs for failed steps onlygh run view <run-id> --log-failed
# Download full logsgh run view <run-id> --log > ci-logs.txt
# Re-run failed jobsgh run rerun <run-id> --failed
# Re-run the entire workflowgh run rerun <run-id>Reproducing Locally
Section titled “Reproducing Locally”Most CI failures can be reproduced locally with the same commands CI uses:
# Run the full CI check suite (matches ci-checks.yml)cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningscargo test --all-features --verbosecargo doc --no-deps --all-featurescargo deny check
# MSRV check (requires the specific toolchain)rustup install 1.92cargo +1.92 check --all-featuresClippy Failures
Section titled “Clippy Failures”Workflow: ci-checks.yml (clippy job)
Command: cargo clippy --all-targets --all-features -- -D warnings
Common Patterns
Section titled “Common Patterns”| Lint | Cause | Fix |
|---|---|---|
clippy::unwrap_used | Called .unwrap() | Use ?, .unwrap_or(), or if let |
clippy::expect_used | Called .expect() | Use ? with proper error types |
clippy::panic | Used panic!() in library code | Return Result or Option |
clippy::todo | Left todo!() in code | Implement the function or remove it |
clippy::dbg_macro | Left dbg!() in code | Remove it or use proper logging |
clippy::print_stdout | Used println!() | Use a logging crate or remove |
clippy::needless_pass_by_value | Function takes ownership unnecessarily | Change parameter to &T |
clippy::redundant_clone | Unnecessary .clone() | Remove the clone |
clippy::missing_errors_doc | Public function returns Result without # Errors doc | Add # Errors section to doc comment |
clippy::missing_panics_doc | Function can panic without # Panics doc | Add doc or remove the panic |
clippy::must_use_candidate | Return value should have #[must_use] | Add the attribute |
How to Fix
Section titled “How to Fix”# Run clippy and see all warningscargo clippy --all-targets --all-features -- -D warnings
# Auto-fix what clippy can fixcargo clippy --fix --all-targets --all-features
# Check a specific filecargo clippy --all-features -- -D warnings 2>&1 | grep "crates/myfile.rs"When to Allow a Lint
Section titled “When to Allow a Lint”Only suppress lints when there is a genuine reason. Add an inline allow with an explanation:
#[allow(clippy::too_many_arguments)] // Builder pattern requires all fieldsfn create_widget(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32) -> Widget { // ...}Never add blanket allows in lib.rs or Cargo.toml to silence clippy globally.
Format Failures
Section titled “Format Failures”Workflow: ci-checks.yml (fmt job)
Command: cargo fmt --all -- --check
# Format all codecargo fmt --all
# Check what would change (without modifying)cargo fmt --all -- --check
# Format a specific filerustfmt crates/lib.rsEditor Integration
Section titled “Editor Integration”Set up your editor to format on save:
VS Code (.vscode/settings.json — already configured in this repo):
{ "editor.formatOnSave": true, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }}Neovim (with rust-analyzer):
vim.api.nvim_create_autocmd("BufWritePre", { pattern = "*.rs", callback = function() vim.lsp.buf.format() end,})Configuration
Section titled “Configuration”This project uses rustfmt.toml (or settings in Cargo.toml). Key settings:
- Line length: 100 characters
- Edition: 2024
If cargo fmt produces unexpected results, check that your local Rust toolchain matches the stable channel used in CI.
MSRV Failures
Section titled “MSRV Failures”Workflow: ci-checks.yml (msrv job)
Command: cargo +1.92 check --all-features
MSRV: Rust 1.92
Common Causes
Section titled “Common Causes”| Cause | Example | Fix |
|---|---|---|
| Used a newer language feature | let-chains (1.87+) | Check which version stabilized the feature |
| Dependency bumped its MSRV | serde requires newer Rust | Pin the dependency to an older version |
| Used a newer std API | std::io::IsTerminal (1.70+) | Use a polyfill or feature-gate |
Diagnosing
Section titled “Diagnosing”# Install and test with the exact MSRVrustup install 1.92cargo +1.92 check --all-features
# Check what version stabilized a feature# Look at the Rust release notes or use:rustup doc --stdFixing
Section titled “Fixing”- If you used a newer feature: Rewrite to use MSRV-compatible syntax
- If a dependency requires newer Rust: Pin to the last compatible version in
Cargo.toml:[dependencies]some-crate = ">=1.0, <1.5" # 1.5 requires Rust 1.93+ - If MSRV needs to be bumped: Update
rust-versioninCargo.toml, update the MSRV check inci-checks.yml, and note it in the changelog as a potentially breaking change
cargo-deny Failures
Section titled “cargo-deny Failures”Workflow: ci-checks.yml (deny job)
Command: cargo deny check
Configuration: deny.toml
Advisory Failures
Section titled “Advisory Failures”error[vulnerability]: RUSTSEC-2024-XXXX: <crate> - <description>Fix: Update the affected dependency:
cargo update -p <crate-name>cargo deny check advisoriesIf no fix is available, add a temporary exception in deny.toml:
[advisories]ignore = [ "RUSTSEC-2024-XXXX", # No fix available; not exploitable in our usage. Revisit by YYYY-MM-DD.]License Failures
Section titled “License Failures”error[rejected]: license 'GPL-3.0' is not in the allow listFix options:
- Remove the dependency and find an alternative with a permissive license
- If the license is acceptable, add it to the allow list in
deny.toml:[licenses]allow = [# ...existing..."NEW-LICENSE-SPDX",]
Ban Failures
Section titled “Ban Failures”error[banned]: crate 'openssl' is bannedFix: The crate (or one of its transitive dependencies) pulls in a banned crate. Check the dependency tree:
cargo tree -i opensslThen either:
- Use an alternative crate that does not depend on the banned one
- Enable a feature flag that uses a different backend (e.g.,
rustlsinstead ofopenssl)
Source Failures
Section titled “Source Failures”error[unknown-registry]: crate 'foo' sourced from unknown registryFix: Only crates from crates.io are allowed. If you need a git dependency, add it to deny.toml:
[sources]allow-git = ["https://github.com/owner/repo"]Running Specific Checks
Section titled “Running Specific Checks”cargo deny check advisoriescargo deny check licensescargo deny check banscargo deny check sourcesTest Failures
Section titled “Test Failures”Workflow: ci-checks.yml (test job, runs on ubuntu, macos, windows)
Command: cargo test --all-features --verbose
Debugging Tips
Section titled “Debugging Tips”# Run all tests with outputcargo test --all-features -- --nocapture
# Run a specific testcargo test test_function_name
# Run tests in a specific modulecargo test module_name::
# Run tests matching a patterncargo test --all-features -- --test-threads=1 pattern
# Run only integration testscargo test --test integration_test
# Run only doc testscargo test --doc
# Show which tests are runningcargo test --all-features -- --listPlatform-Specific Failures
Section titled “Platform-Specific Failures”If tests pass on one OS but fail on another:
| Symptom | Common cause | Fix |
|---|---|---|
| Path separator issues | Hardcoded / or \ | Use std::path::PathBuf |
| Line ending issues | \n vs \r\n | Normalize with .trim() or use \r?\n in regex |
| Temp directory issues | Different temp path formats | Use std::env::temp_dir() |
| File permission issues | Unix permissions on Windows | Feature-gate Unix-specific code |
Flaky Tests
Section titled “Flaky Tests”If a test passes sometimes and fails sometimes:
- Run it many times:
for i in $(seq 100); do cargo test test_name || break; done - Check for race conditions in async code
- Check for reliance on system time
- Check for reliance on HashMap iteration order
CodeQL Failures
Section titled “CodeQL Failures”Workflow: quality-gates.yml (the sast job; SAST runs via the central
reusable-sast-codeql.yml)
Schedule: Weekly (Monday 06:00 UTC) + pushes to main
Common Findings
Section titled “Common Findings”| Finding | Description | Fix |
|---|---|---|
| Uncontrolled format string | User input in format!() | Sanitize or use {} placeholder |
| Path traversal | User input in file paths | Validate and canonicalize paths |
| Command injection | User input in shell commands | Avoid shell; use Command::new() |
| Integer overflow | Unchecked arithmetic | Use .checked_add() or wrapping_add() |
False Positives
Section titled “False Positives”CodeQL uses the cpp extractor for Rust, which can produce false positives. To dismiss:
- Review the finding in Security > Code scanning alerts
- If it is a false positive, click “Dismiss alert” and select a reason
- Add a comment explaining why it is not a real issue
Running CodeQL Locally
Section titled “Running CodeQL Locally”# Install CodeQL CLI: https://github.com/github/codeql-cli-binariescodeql database create my-db --language=cpp --command="cargo build --all-features"codeql database analyze my-db --format=sarif-latest --output=results.sarifRelease Workflow Failures
Section titled “Release Workflow Failures”Workflow: release.yml
Trigger: Push of v*.*.* tag
Build Failures
Section titled “Build Failures”| Error | Cause | Fix |
|---|---|---|
Cargo.toml version mismatch | Version in Cargo.toml does not match the tag | Ensure version = "X.Y.Z" matches tag vX.Y.Z |
no [[bin]] target found in Cargo.toml | The meta job resolves the binary name from cargo metadata | Ensure Cargo.toml declares a [[bin]] target |
error[E0658]: feature not available | Target requires newer Rust | Check MSRV compatibility for all targets |
| macos-amd64 build fails | Cross-target build (x86_64-apple-darwin on macos-latest) | Check the targets: input on the toolchain step; all other legs build natively |
Missing Release Assets
Section titled “Missing Release Assets”If the release is created but some binaries are missing:
# Check which assets existgh release view vX.Y.Z
# Re-run just the failed matrix jobgh run rerun <run-id> --failedPublish or Attestation Failures
Section titled “Publish or Attestation Failures”crates.io publishing (publish.yml) uses Trusted Publishing (OIDC) — there is no registry token secret. If authentication fails with “No Trusted Publishing config found”, complete the one-time setup on crates.io: crate Settings > Trusted Publishing > add this repo with workflow publish.yml and environment copilot.
# Verify secrets are configured (you can't read them, just confirm they exist)gh secret listSecrets and permissions involved:
HOMEBREW_TAP_TOKENfor Homebrew formula pushes (package-homebrew.yml)id-token: writeandattestations: writejob permissions for attestation stepsGITHUB_TOKENis automatic
Docker Build Failures
Section titled “Docker Build Failures”Workflow: docker.yml
Trigger: Push to main, tag push, or PR (build-only)
Common Issues
Section titled “Common Issues”| Error | Cause | Fix |
|---|---|---|
failed to solve: Dockerfile: not found | Missing Dockerfile | Ensure Dockerfile exists in repo root |
COPY failed: file not found | Wrong path in Dockerfile | Check paths match crates/ structure |
denied: denied on push | Missing permissions | Verify packages: write in workflow and “Read and write permissions” in repo settings |
ERROR: Multiple platforms not supported | Buildx not configured | The workflow sets up Buildx; check that step |
| Build arg mismatch | RUST_VERSION arg wrong | Check build-args in the workflow matches a valid Rust version |
Testing Docker Locally
Section titled “Testing Docker Locally”# Build for current platform onlydocker build -t rust-template:test .
# Run the test imagedocker run --rm rust-template:test --version
# Build for multiple platforms (requires buildx)docker buildx build --platform linux/amd64,linux/arm64 -t rust-template:test .Dependabot Merge Conflicts
Section titled “Dependabot Merge Conflicts”When Dependabot PRs have merge conflicts in Cargo.lock:
Automatic Resolution
Section titled “Automatic Resolution”Close and reopen the PR to trigger Dependabot to rebase:
gh pr close <number>gh pr reopen <number>Or comment on the PR:
gh pr comment <number> --body "@dependabot rebase"Manual Resolution
Section titled “Manual Resolution”# Checkout the Dependabot branchgh pr checkout <number>
# Merge main into itgit merge main
# Resolve Cargo.lock conflicts by regenerating itgit checkout --theirs Cargo.lockcargo update
# Or if specific crate needs updatingcargo update -p <crate-from-dependabot-pr>
# Commit and pushgit add Cargo.lockgit commit -m "chore: resolve merge conflict in Cargo.lock"git pushUseful Dependabot Commands
Section titled “Useful Dependabot Commands”Comment these on any Dependabot PR:
| Command | Effect |
|---|---|
@dependabot rebase | Rebase the PR on the latest base branch |
@dependabot recreate | Close and recreate the PR from scratch |
@dependabot merge | Merge the PR (if CI passes) |
@dependabot squash and merge | Squash merge the PR |
@dependabot cancel merge | Cancel a pending auto-merge |
@dependabot ignore this major version | Stop updates for this major version |
@dependabot ignore this dependency | Stop all updates for this dependency |
Documentation Failures
Section titled “Documentation Failures”Workflow: ci-checks.yml (doc job)
Command: cargo doc --no-deps --all-features
Environment: RUSTDOCFLAGS="-D warnings"
Common Causes
Section titled “Common Causes”| Error | Cause | Fix |
|---|---|---|
unresolved link | Broken intra-doc link [Type] | Fix the type name or path in the doc comment |
missing documentation | Public item without /// doc | Add documentation to the public item |
private item in public doc | Doc references a private type | Make the type public or remove the reference |
broken code example | Doc test does not compile | Fix the code in the ```rust block |
Testing Documentation Locally
Section titled “Testing Documentation Locally”# Build docs with warnings as errors (matches CI)RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
# Open the docs in a browsercargo doc --open
# Run only doc testscargo test --docCoverage Failures
Section titled “Coverage Failures”Workflow: pipeline.yml (coverage job) — this job does not block merging (fail_ci_if_error: false on Codecov upload)
If coverage generation fails:
# Install cargo-llvm-covcargo install cargo-llvm-cov
# Generate coverage locallycargo llvm-cov --all-features --lcov --output-path lcov.info
# Generate an HTML reportcargo llvm-cov --all-features --htmlopen target/llvm-cov/html/index.htmlQuick Reference: CI Jobs and Commands
Section titled “Quick Reference: CI Jobs and Commands”| CI Job | Local equivalent | Blocks merge? |
|---|---|---|
| Format | cargo fmt --all -- --check | Yes |
| Clippy | cargo clippy --all-targets --all-features -- -D warnings | Yes |
| Test (ubuntu) | cargo test --all-features --verbose | Yes |
| Test (macos) | Same (run on macOS) | Yes |
| Test (windows) | Same (run on Windows) | Yes |
| Documentation | RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features | Yes |
| Cargo Deny | cargo deny check | Yes |
| MSRV | cargo +1.92 check --all-features | Yes |
| Coverage | cargo llvm-cov --all-features | No |
| CodeQL | Build + static analysis | No (scheduled) |
| Security Audit | cargo audit --deny warnings | No (scheduled) |
Run Everything at Once
Section titled “Run Everything at Once”cargo fmt --all -- --check \ && cargo clippy --all-targets --all-features -- -D warnings \ && cargo test --all-features --verbose \ && RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features \ && cargo deny check \ && cargo +1.92 check --all-features