Heads up: This project is still early. A good chunk of the code was AI-generated and is being actively reviewed, tested, and improved. All tests pass and the benchmarks are reproducible, but treat this as experimental for now. Bug reports and contributions are very welcome as we work toward a stable release.
A fast GitHub Actions workflow validator written in Rust. Truss catches configuration errors, semantic issues, and common mistakes in your CI/CD pipelines before you push — so you spend less time debugging failed runs.
Try it in your browser — no install required.
It's fast and accurate. We tested Truss, actionlint, yamllint, and yaml-language-server against 271 production workflow files from 7 major open-source projects (pytorch/pytorch, rust-lang/rust, tensorflow/tensorflow, facebook/react, microsoft/TypeScript, kubernetes/kubernetes, actions/checkout):
| Tool | Language | Errors | False Positives | Avg Time | Total Time |
|---|---|---|---|---|---|
| Truss | Rust | 0 | 0 | 1.7 ms | 471 ms |
| actionlint | Go | 0 | 0 | 6.8 ms | 1,840 ms |
| yaml-language-server | TypeScript | 0 | 0 | 101 ms | 27,277 ms |
| yamllint | Python | 0 | n/a (style only) | 123 ms | 33,453 ms |
All tools reported zero errors on 271 real-world files — a clean bill of health across the board. The key differentiator is speed: Truss processes each file in ~1.7ms on average, making it 3.9x faster than actionlint, 58x faster than yaml-language-server, and 71x faster than yamllint.
Measured on an Intel i5-7500T @ 2.70GHz, 8GB RAM, Linux 6.17. Your results may vary. See test-suite/ for how to reproduce.
Truss ships with 41 validation rules that go well beyond syntax checking. It validates job dependencies for circular references, checks that your runs-on labels are real GitHub-hosted runners, flags script injection risks, warns about deprecated workflow commands, verifies matrix strategies, validates cron expressions, and much more.
See the full rule list below or check docs/VALIDATION_RULES.md for the details.
| Feature | Truss | actionlint | yamllint | yaml-language-server |
|---|---|---|---|---|
| Language | Rust | Go | Python | TypeScript |
| YAML syntax | tree-sitter | Custom | Yes | JSON Schema |
| GHA semantic validation | 41 rules | Yes | No | Partial (schema) |
| Expression validation | ${{ }} syntax, functions, operators |
Strong type-checking | No | No |
| Runner label checks | 22+ labels | Yes | No | No |
| Matrix validation | Structure + keys | Structure + types | No | No |
| Reusable workflows | Inputs, outputs, secrets | Inputs, outputs, secrets | No | No |
| Job dependency cycles | Yes | Yes | No | No |
| Script injection detection | Yes | Yes (+ shellcheck) | No | No |
| Cron validation | Yes | Yes | No | No |
| Action reference format | Yes (incl. subpaths) | Yes | No | No |
| LSP server | Yes (incremental) | No (requested) | No | Yes |
| VS Code extension | Yes | Yes (2 extensions) | No | Yes |
| Avg time per file | 1.7 ms | 6.8 ms | 123 ms | 101 ms |
Other tools in the ecosystem: zizmor (Rust, security-focused, 24 audit rules), action-validator (Rust, schema-based), ghalint (Go, security policies). These are complementary — they focus on security auditing or schema validation rather than semantic correctness.
# From crates.io (recommended)
cargo install truss-cli
# Or download a prebuilt binary from the latest release
# https://github.com/JuanMarchetto/truss/releases
# Or build from source
git clone https://github.com/JuanMarchetto/truss.git
cd truss
cargo build --release# Validate a workflow file
truss validate .github/workflows/ci.yml
# Validate everything in a directory
truss validate .github/workflows/
# Multiple files at once (parallel processing)
truss validate ci.yml deploy.yml release.yml
# Glob patterns work too
truss validate '.github/workflows/*.yml'
# Pipe from stdin
cat workflow.yml | truss validate -
# Only show errors (skip warnings)
truss validate --severity error ci.yml
# Machine-readable JSON output
truss validate --json ci.yml
# Quiet mode — just the exit code
truss validate --quiet ci.yml
# Version info
truss --version| Code | Meaning |
|---|---|
| 0 | All files valid |
| 1 | Validation errors found |
| 2 | Bad arguments or no files given |
| 3 | I/O error (file not found, permission denied) |
There's a VS Code extension in editors/vscode/ that gives you real-time diagnostics as you type:
cargo build --release -p truss-lsp
cd editors/vscode
npm install && npm run compile
npx vsce package
code --install-extension truss-validator-0.1.0.vsixIt activates automatically on .github/workflows/*.yml files. See editors/vscode/README.md for setup details.
You can hook up truss-lsp to any editor that supports LSP:
./target/release/truss-lsp # stdio transportPoint your editor's LSP client at this binary for .github/workflows/*.yml files. It supports incremental parsing, so re-validation after edits is near-instant.
41 rules across 5 categories:
| Rule | What it does |
|---|---|
| SyntaxRule | YAML syntax validation via tree-sitter |
| NonEmptyRule | Catches empty documents |
| GitHubActionsSchemaRule | Validates basic workflow structure |
| WorkflowTriggerRule | on: trigger config (30+ event types) |
| Rule | What it does |
|---|---|
| JobNameRule | Duplicate names, invalid characters, reserved words |
| JobNeedsRule | Dependency validation, circular dependency detection |
| JobIfExpressionRule | Conditional expression validation |
| JobOutputsRule | Output reference validation |
| JobContainerRule | Container image, ports, services config |
| JobStrategyValidationRule | Strategy structure validation |
| RunsOnRequiredRule | Makes sure every job has runs-on |
| RunnerLabelRule | Validates GitHub-hosted runner labels (22+ labels) |
| ReusableWorkflowCallRule | Reusable workflow path and structure |
| Rule | What it does |
|---|---|
| StepValidationRule | Step structure — must have uses or run (not both) |
| StepNameRule | Step name format validation |
| StepIdUniquenessRule | No duplicate step IDs within a job |
| StepIfExpressionRule | Step conditional expressions |
| StepOutputReferenceRule | steps.X.outputs.Y reference validation |
| StepContinueOnErrorRule | Boolean validation for continue-on-error |
| StepTimeoutRule | Timeout value validation |
| StepShellRule | Shell type validation (bash, pwsh, python, etc.) |
| StepWorkingDirectoryRule | Working directory path validation |
| StepEnvValidationRule | Env var names + reserved GITHUB_ prefix detection |
| ArtifactValidationRule | upload/download-artifact parameter validation |
| Rule | What it does |
|---|---|
| WorkflowNameRule | Workflow name validation |
| WorkflowInputsRule | Input types and requirements |
| WorkflowCallInputsRule | Reusable workflow call inputs |
| WorkflowCallSecretsRule | Secret definitions and references |
| WorkflowCallOutputsRule | Workflow call output validation |
| TimeoutRule | Job-level timeout validation |
| PermissionsRule | Permission scope validation (15+ scopes) |
| ConcurrencyRule | Concurrency groups and cancel-in-progress |
| DefaultsValidationRule | Default shell and working directory |
| Rule | What it does |
|---|---|
| ExpressionValidationRule | ${{ }} syntax, functions, operators |
| ActionReferenceRule | owner/repo@ref format validation |
| EventPayloadValidationRule | Event fields, filter conflicts, cron ranges, activity types |
| SecretsValidationRule | Secret reference format and naming |
| MatrixStrategyRule | Matrix structure and key validation |
| EnvironmentRule | Environment name format |
| ScriptInjectionRule | Flags untrusted inputs used directly in run: blocks |
| DeprecatedCommandsRule | Warns about ::set-output, ::set-env, etc. |
We cloned 7 major open-source repositories and ran every tool against all 271 workflow files found across pytorch/pytorch (139 files), rust-lang/rust (50), tensorflow/tensorflow (33), facebook/react (24), microsoft/TypeScript (18), actions/checkout (7):
| Tool | Errors | False Positives | Avg Time | Total Time | Speedup |
|---|---|---|---|---|---|
| Truss | 0 | 0 | 1.7 ms | 471 ms | — |
| actionlint | 0 | 0 | 6.8 ms | 1,840 ms | Truss is 3.9x faster |
| yaml-language-server | 0 | 0 | 101 ms | 27,277 ms | Truss is 58x faster |
| yamllint | 0 | n/a | 123 ms | 33,453 ms | Truss is 71x faster |
All four tools report zero errors on these 271 files — the test repos' workflows are well-formed. The differentiator here is pure speed: Truss validates each file in about 1.7ms, while actionlint takes 6.8ms, and the Python/TypeScript-based tools take 101-123ms.
To reproduce: cd test-suite && bash scripts/setup-test-repos.sh && bash scripts/run-full-suite.sh
End-to-end timing of truss validate --quiet with --shell=none, 200 runs:
| Fixture | Mean | Min | Max |
|---|---|---|---|
| Simple (14 lines) | 1.7 ms | 1.4 ms | 4.4 ms |
| Medium (multi-step) | 2.4 ms | 2.1 ms | 5.9 ms |
| Complex dynamic (reusable calls) | 3.7 ms | 3.3 ms | 5.1 ms |
| Complex static (matrices) | 4.9 ms | 4.4 ms | 6.3 ms |
| All 4 files (directory scan) | 5.7 ms | 5.0 ms | 7.8 ms |
All tools benchmarked on the same machine (Intel i5-7500T @ 2.70GHz, 8GB RAM, Linux 6.17) with Hyperfine (--shell=none, 200 runs, --warmup 10). This is one particular benchmark on one particular machine — your results may vary.
| Fixture | Truss (Rust) | actionlint (Go) | yamllint (Python) |
|---|---|---|---|
| Simple | 1.7 ms | 2.6 ms | 103 ms |
| Complex dynamic | 3.7 ms | 3.7 ms | 140 ms |
| Complex static | 4.9 ms | 4.0 ms | 153 ms |
On individual fixtures, Truss and actionlint are in the same performance class (single-digit milliseconds). Truss is faster on simple files; actionlint is faster on complex ones. Both are 30-60x faster than yamllint. On larger batches (271 real-world files), Truss averages 1.7ms/file vs. actionlint's 6.8ms/file — a 3.9x advantage that comes from Truss's lower per-file overhead, LTO-optimized binary, and parallel rule execution.
just bench # Criterion (core engine)
just bench-cli # Hyperfine (CLI end-to-end)
just compare # Compare against competitors (requires actionlint, yamllint)truss/
├── crates/
│ ├── truss-core/ # Validation engine — editor-agnostic, deterministic
│ │ ├── lib.rs # Engine with 41 registered rules
│ │ ├── parser.rs # tree-sitter YAML parser (incremental)
│ │ ├── validation/ # 41 rule implementations
│ │ ├── tests/ # 45 test files, 403 tests
│ │ └── benches/ # Criterion benchmarks
│ ├── truss-cli/ # CLI — parallel processing, globs, stdin, JSON output
│ ├── truss-lsp/ # Language Server Protocol adapter
│ └── truss-wasm/ # WebAssembly bindings
├── editors/
│ └── vscode/ # VS Code extension
├── benchmarks/ # Fixtures and Hyperfine results
├── competitors/ # Comparison scripts for actionlint, yamllint, etc.
├── test-suite/ # Multi-repo comparison testing framework
├── scripts/ # Build, test, and comparison automation
├── docs/ # Architecture, rules, test strategy docs
└── .github/workflows/ # CI pipeline
# Debug build
just build-debug # or: cargo build --workspace
# Release build
just build # or: cargo build --workspace --release
# Run all 403 tests
just test # or: cargo test --workspace
# Core tests only
just test-core # or: cargo test -p truss-coreEvery push to main and every PR runs:
cargo check --workspacecargo test --workspace(403 tests)cargo clippy --workspace -- -D warningscargo fmt --all -- --check
Truss follows a "core first" design. All validation logic lives in truss-core, which is editor-agnostic and fully deterministic. The CLI, LSP server, and WASM crate are thin adapters that wrap the core for different interfaces.
Key ideas:
- Performance is a feature, not an afterthought — everything is benchmarked
- Rules are stateless — each rule gets the parsed tree and source, returns diagnostics
- Results are deterministic — same input always produces the same output
- Incremental parsing — the LSP server only re-parses what changed
More details in docs/ARCHITECTURE.md.
What's working:
- 41 validation rules with unique rule IDs, all tested (403 tests across 45 test files)
- Zero false positives on 271 production workflow files from pytorch, rust-lang, tensorflow, react, TypeScript, kubernetes, and checkout
- LSP server with real-time diagnostics and incremental parsing
- VS Code extension
- CLI with parallel file processing, globs, stdin, severity filtering, rule filtering (
--ignore-rules,--only-rules), JSON output .truss.ymlconfiguration file support (ignore paths, enable/disable rules per project)- Sub-6ms validation per file, 3.9x faster than actionlint on real-world batches
- WASM bindings and online playground
- CI pipeline (check, test, clippy, fmt)
Coming next:
- Contextual autocomplete
- Neovim and other editor integrations
Out of scope (for now):
- Azure Pipelines / GitLab CI
- Advanced UI
- Architecture — Design principles and guidelines
- Validation Rules — All 41 rules in detail
- Test Strategy — How we test
- Planned Improvements — What's on the roadmap
Contributions are welcome! See the Contributing Guide to get started.
MIT. See LICENSE-MIT.